arel_toolkit 0.2.0 → 0.4.3
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +3 -0
- data/.github/workflows/develop.yml +90 -0
- data/.github/workflows/master.yml +67 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +13 -2
- data/Appraisals +13 -0
- data/CHANGELOG.md +132 -6
- data/Gemfile +5 -0
- data/Gemfile.lock +92 -12
- data/Guardfile +23 -12
- data/README.md +104 -6
- data/Rakefile +18 -0
- data/arel_toolkit.gemspec +19 -4
- data/benchmark.rb +54 -0
- data/bin/console +1 -0
- data/ext/pg_result_init/extconf.rb +52 -0
- data/ext/pg_result_init/pg_result_init.c +138 -0
- data/ext/pg_result_init/pg_result_init.h +6 -0
- data/gemfiles/active_record_6.gemfile +7 -0
- data/gemfiles/active_record_6.gemfile.lock +210 -0
- data/gemfiles/arel_gems.gemfile +10 -0
- data/gemfiles/arel_gems.gemfile.lock +284 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/default.gemfile.lock +208 -0
- data/lib/arel/enhance.rb +17 -0
- data/lib/arel/enhance/context_enhancer/arel_table.rb +92 -0
- data/lib/arel/enhance/node.rb +232 -0
- data/lib/arel/enhance/path.rb +38 -0
- data/lib/arel/enhance/path_node.rb +26 -0
- data/lib/arel/enhance/query.rb +38 -0
- data/lib/arel/enhance/query_methods.rb +23 -0
- data/lib/arel/enhance/visitor.rb +97 -0
- data/lib/arel/extensions.rb +55 -3
- data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
- data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
- data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
- data/lib/arel/extensions/active_record_type_caster_map.rb +7 -0
- data/lib/arel/extensions/array.rb +2 -9
- data/lib/arel/extensions/assignment.rb +22 -0
- data/lib/arel/extensions/at_time_zone.rb +37 -0
- data/lib/arel/extensions/attributes_attribute.rb +47 -0
- data/lib/arel/extensions/binary.rb +7 -0
- data/lib/arel/extensions/bind_param.rb +15 -0
- data/lib/arel/extensions/bit_string.rb +2 -9
- data/lib/arel/extensions/case.rb +17 -0
- data/lib/arel/extensions/coalesce.rb +17 -3
- data/lib/arel/extensions/conflict.rb +9 -0
- data/lib/arel/extensions/contained_within_equals.rb +10 -0
- data/lib/arel/extensions/contains.rb +27 -5
- data/lib/arel/extensions/contains_equals.rb +10 -0
- data/lib/arel/extensions/current_catalog.rb +4 -0
- data/lib/arel/extensions/current_date.rb +4 -0
- data/lib/arel/extensions/current_of_expression.rb +2 -9
- data/lib/arel/extensions/current_role.rb +4 -0
- data/lib/arel/extensions/current_row.rb +7 -0
- data/lib/arel/extensions/current_schema.rb +4 -0
- data/lib/arel/extensions/current_user.rb +4 -0
- data/lib/arel/extensions/dealocate.rb +31 -0
- data/lib/arel/extensions/default_values.rb +4 -0
- data/lib/arel/extensions/delete_manager.rb +25 -0
- data/lib/arel/extensions/delete_statement.rb +32 -8
- data/lib/arel/extensions/distinct_from.rb +3 -16
- data/lib/arel/extensions/dot.rb +11 -0
- data/lib/arel/extensions/equality.rb +2 -4
- data/lib/arel/extensions/exists.rb +59 -0
- data/lib/arel/extensions/extract_from.rb +25 -0
- data/lib/arel/extensions/factorial.rb +10 -2
- data/lib/arel/extensions/false.rb +7 -0
- data/lib/arel/extensions/function.rb +44 -14
- data/lib/arel/extensions/greatest.rb +17 -3
- data/lib/arel/extensions/indirection.rb +3 -12
- data/lib/arel/extensions/infer.rb +7 -7
- data/lib/arel/extensions/infix_operation.rb +17 -0
- data/lib/arel/extensions/insert_manager.rb +21 -0
- data/lib/arel/extensions/insert_statement.rb +35 -9
- data/lib/arel/extensions/into.rb +21 -0
- data/lib/arel/extensions/json_get_field.rb +10 -0
- data/lib/arel/extensions/json_get_object.rb +10 -0
- data/lib/arel/extensions/json_path_get_field.rb +10 -0
- data/lib/arel/extensions/json_path_get_object.rb +10 -0
- data/lib/arel/extensions/jsonb_all_key_exists.rb +10 -0
- data/lib/arel/extensions/jsonb_any_key_exists.rb +10 -0
- data/lib/arel/extensions/jsonb_key_exists.rb +10 -0
- data/lib/arel/extensions/least.rb +17 -3
- data/lib/arel/extensions/named_argument.rb +24 -0
- data/lib/arel/extensions/named_function.rb +7 -0
- data/lib/arel/extensions/node.rb +10 -0
- data/lib/arel/extensions/not_distinct_from.rb +3 -16
- data/lib/arel/extensions/not_equal.rb +2 -4
- data/lib/arel/extensions/ordering.rb +21 -6
- data/lib/arel/extensions/overlap.rb +1 -1
- data/lib/arel/extensions/overlaps.rb +49 -0
- data/lib/arel/extensions/overlay.rb +53 -0
- data/lib/arel/extensions/position.rb +27 -0
- data/lib/arel/extensions/prepare.rb +39 -0
- data/lib/arel/extensions/range_function.rb +10 -2
- data/lib/arel/extensions/row.rb +3 -8
- data/lib/arel/extensions/select_core.rb +73 -0
- data/lib/arel/extensions/select_manager.rb +25 -0
- data/lib/arel/extensions/select_statement.rb +31 -9
- data/lib/arel/extensions/session_user.rb +4 -0
- data/lib/arel/extensions/set_to_default.rb +4 -0
- data/lib/arel/extensions/substring.rb +46 -0
- data/lib/arel/extensions/table.rb +43 -10
- data/lib/arel/extensions/time_with_precision.rb +6 -0
- data/lib/arel/extensions/to_sql.rb +27 -0
- data/lib/arel/extensions/top.rb +8 -0
- data/lib/arel/extensions/transaction.rb +45 -0
- data/lib/arel/extensions/tree_manager.rb +15 -0
- data/lib/arel/extensions/trim.rb +44 -0
- data/lib/arel/extensions/true.rb +7 -0
- data/lib/arel/extensions/type_cast.rb +11 -0
- data/lib/arel/extensions/unary.rb +7 -0
- data/lib/arel/extensions/unary_operation.rb +16 -0
- data/lib/arel/extensions/unknown.rb +4 -0
- data/lib/arel/extensions/update_manager.rb +25 -0
- data/lib/arel/extensions/update_statement.rb +31 -6
- data/lib/arel/extensions/user.rb +4 -0
- data/lib/arel/extensions/values_list.rb +15 -0
- data/lib/arel/extensions/variable_set.rb +55 -0
- data/lib/arel/extensions/variable_show.rb +26 -0
- data/lib/arel/middleware.rb +27 -0
- data/lib/arel/middleware/active_record_extension.rb +13 -0
- data/lib/arel/middleware/cache_accessor.rb +35 -0
- data/lib/arel/middleware/chain.rb +172 -0
- data/lib/arel/middleware/database_executor.rb +77 -0
- data/lib/arel/middleware/no_op_cache.rb +9 -0
- data/lib/arel/middleware/postgresql_adapter.rb +62 -0
- data/lib/arel/middleware/railtie.rb +25 -0
- data/lib/arel/middleware/result.rb +170 -0
- data/lib/arel/middleware/to_sql_executor.rb +15 -0
- data/lib/arel/middleware/to_sql_middleware.rb +33 -0
- data/lib/arel/sql_to_arel.rb +8 -4
- data/lib/arel/sql_to_arel/error.rb +6 -0
- data/lib/arel/sql_to_arel/pg_query_visitor.rb +324 -76
- data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +112 -0
- data/lib/arel/sql_to_arel/result.rb +30 -0
- data/lib/arel/transformer.rb +8 -0
- data/lib/arel/transformer/prefix_schema_name.rb +183 -0
- data/lib/arel/transformer/remove_active_record_info.rb +40 -0
- data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
- data/lib/arel_toolkit.rb +16 -1
- data/lib/arel_toolkit/version.rb +1 -1
- metadata +278 -25
- data/.travis.yml +0 -21
- data/lib/arel/extensions/generate_series.rb +0 -9
- data/lib/arel/extensions/rank.rb +0 -9
- data/lib/arel/sql_to_arel/frame_options.rb +0 -110
- data/lib/arel/sql_to_arel/unbound_column_reference.rb +0 -5
data/Guardfile
CHANGED
|
@@ -24,19 +24,30 @@
|
|
|
24
24
|
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
|
25
25
|
# * 'just' rspec: 'rspec'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
group :red_green_refactor, halt_on_fail: true do
|
|
28
|
+
guard :rspec, cmd: 'bundle exec rspec', failed_mode: :focus do
|
|
29
|
+
require 'guard/rspec/dsl'
|
|
30
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# Feel free to open issues for suggestions and improvements
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
# RSpec files
|
|
35
|
+
rspec = dsl.rspec
|
|
36
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
|
37
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
|
38
|
+
watch(rspec.spec_files)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# Ruby files
|
|
41
|
+
ruby = dsl.ruby
|
|
42
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
guard :rubocop do
|
|
46
|
+
watch(/.+\.rb$/)
|
|
47
|
+
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
guard 'rake', task: 'compile' do
|
|
52
|
+
watch(/^ext/)
|
|
42
53
|
end
|
data/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
- [](https://github.com/mvgijssel/arel_toolkit/actions)
|
|
6
6
|
- [](https://codeclimate.com/github/mvgijssel/arel_toolkit/maintainability)
|
|
7
7
|
- [](https://codeclimate.com/github/mvgijssel/arel_toolkit/test_coverage)
|
|
8
8
|
- [](https://badge.fury.io/rb/arel_toolkit)
|
|
9
9
|
- [](https://opensource.org/licenses/MIT)
|
|
10
10
|
- 
|
|
11
11
|
- 
|
|
12
|
+
- [Coverage report](https://mvgijssel.github.io/arel_toolkit/)
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -26,9 +27,9 @@ Or install it yourself as:
|
|
|
26
27
|
|
|
27
28
|
$ gem install arel_toolkit
|
|
28
29
|
|
|
29
|
-
##
|
|
30
|
+
## Sql to Arel
|
|
30
31
|
|
|
31
|
-
Convert your (PostgreSQL) SQL into Arel.
|
|
32
|
+
Convert your (PostgreSQL) SQL into an Arel AST.
|
|
32
33
|
|
|
33
34
|
```ruby
|
|
34
35
|
[1] > sql = 'SELECT id FROM users'
|
|
@@ -39,9 +40,106 @@ Convert your (PostgreSQL) SQL into Arel.
|
|
|
39
40
|
=> "SELECT \"id\" FROM \"users\""
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
## Enhanced Arel AST
|
|
44
|
+
|
|
45
|
+
`Arel.enhance(arel)` adds additional information and helper methods to the existing Arel AST. This allows for mutating the AST, adding contextual information to the AST and querying for nodes. Some examples:
|
|
46
|
+
|
|
47
|
+
##### Query for Arel nodes with certain properties
|
|
48
|
+
```ruby
|
|
49
|
+
arel = Post.select(:id, :public).where(id: 1).arel
|
|
50
|
+
enhanced_arel = Arel.enhance(arel)
|
|
51
|
+
enhanced_arel.query(class: Arel::Table).each { ... }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
##### Query for Arel nodes with an enhanced context
|
|
55
|
+
An `Arel::Table` is used in multiple different places inside the AST, and those locations will give the `Arel::Table` a different meaning. Used within a projection (_column_reference_) like `SELECT posts.id` has a different meaning than within a from `SELECT * FROM posts` (_range_variable_). The following example results in `Arel::Table` nodes where the object is used in the context of referencing a column:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
enhanced_arel.query(class: Arel::Table, context: { column_reference: true }).each { ... }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
##### Get an Arel node at a certain path
|
|
62
|
+
```ruby
|
|
63
|
+
enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).object
|
|
64
|
+
=> #<struct Arel::Attributes::Attribute>
|
|
65
|
+
```
|
|
66
|
+
|
|
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. The `replace` method accepts any Arel node or a precomputed enhanced node for improved performance.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).replace(Post.arel_table[:content])
|
|
72
|
+
enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 0]).remove
|
|
73
|
+
enhanced_arel.to_sql
|
|
74
|
+
=> SELECT "posts"."content" FROM "posts" WHERE "posts"."id" = $1
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Middleware
|
|
79
|
+
|
|
80
|
+
Creating Arel from SQL and enhancing Arel is just the beginning, where this gem really shines is the ability to modify Arel ASTs using middleware.
|
|
81
|
+
|
|
82
|
+
Middleware sits between ActiveRecord and the database, it allows you to alter the Arel (the SQL query) before it's send to the database. Multiple middlewares are supported by passing the results from a finished middleware to the next. Next to the arel object, a context object is used that acts as a intermediate storage between middlewares.
|
|
83
|
+
|
|
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
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
Arel::Middleware::Railtie.insert
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Example
|
|
91
|
+
|
|
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.
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class ReorderMiddleware
|
|
98
|
+
def self.call(arel, next_middleware)
|
|
99
|
+
enhanced_arel = Arel.enhance(arel)
|
|
100
|
+
enhanced_arel.query(class: Arel::Nodes::SelectStatement).each do |node|
|
|
101
|
+
arel_table = node.child_at_path(['cores', 0, 'source', 'left']).object
|
|
102
|
+
node['orders'].replace([arel_table[:id].asc])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
new_arel = arel.order(Post.arel_table[:id].asc)
|
|
106
|
+
next_middleware.call(new_arel)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class LoggingMiddleware
|
|
111
|
+
def self.call(arel, next_middleware, context)
|
|
112
|
+
puts "User executing query: `#{context[:current_user_id]}`"
|
|
113
|
+
puts "Original SQL: `#{context[:original_sql]}`"
|
|
114
|
+
puts "Modified SQL: `#{arel.to_sql}`"
|
|
115
|
+
|
|
116
|
+
next_middleware.call(arel)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Now that we've defined our middelwares, it's time to see them in action:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
[1] > Arel.middleware.apply([ReorderMiddleware, LoggingMiddleware]).context(current_user_id: 1) { Post.all.load }
|
|
125
|
+
User executing query: `1`
|
|
126
|
+
Original SQL: `SELECT "posts".* FROM "posts"`
|
|
127
|
+
Modified SQL: `SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC`
|
|
128
|
+
Post Load (4.1ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC
|
|
129
|
+
=> []
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This gem ships with a couple of middelware methods that allow you to fine-tune what and when to apply middelware.
|
|
133
|
+
- `Arel.middleware.apply([SomeMiddleware]) { ... }`
|
|
134
|
+
- `Arel.middleware.only([OnlyMe]) { ... }`
|
|
135
|
+
- `Arel.middleware.none { ... }`
|
|
136
|
+
- `Arel.middleware.except(RemoveMe) { ... }`
|
|
137
|
+
- `Arel.middleware.insert_before(RunBefore, ThisMiddleware) { ... }`
|
|
138
|
+
- `Arel.middleware.insert_after(RunAfter, ThisMiddleware) { ... }`
|
|
139
|
+
|
|
42
140
|
## Extensions
|
|
43
141
|
|
|
44
|
-
|
|
142
|
+
This gem aims to have full support for PostgreSQL's SQL. In order to do so, it needs to add missing Arel nodes and extends the existing visitors. A full list of extensions on Arel can be found here: [lib/arel/extensions](https://github.com/mvgijssel/arel_toolkit/tree/master/lib/arel/extensions).
|
|
45
143
|
|
|
46
144
|
## Development
|
|
47
145
|
|
|
@@ -51,7 +149,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
51
149
|
|
|
52
150
|
## Contributing
|
|
53
151
|
|
|
54
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
152
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mvgijssel/arel_toolkit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
55
153
|
|
|
56
154
|
## License
|
|
57
155
|
|
|
@@ -59,4 +157,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
59
157
|
|
|
60
158
|
## Code of Conduct
|
|
61
159
|
|
|
62
|
-
Everyone interacting in the ArelToolkit project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
160
|
+
Everyone interacting in the ArelToolkit project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/mvgijssel/arel_toolkit/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rspec/core/rake_task'
|
|
3
|
+
require 'github_changelog_generator/task'
|
|
3
4
|
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
|
5
6
|
|
|
6
7
|
task default: :spec
|
|
8
|
+
|
|
9
|
+
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
|
10
|
+
config.user = 'mvgijssel'
|
|
11
|
+
config.project = 'arel_toolkit'
|
|
12
|
+
config.future_release = "v#{ArelToolkit::VERSION}"
|
|
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'
|
|
24
|
+
end
|
data/arel_toolkit.gemspec
CHANGED
|
@@ -23,22 +23,37 @@ 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 '
|
|
28
|
-
spec.add_dependency '
|
|
28
|
+
spec.add_dependency 'activerecord'
|
|
29
|
+
spec.add_dependency 'pg', '~> 1.1.4'
|
|
30
|
+
spec.add_dependency 'pg_query', '~> 1.2.0'
|
|
29
31
|
|
|
30
32
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
31
|
-
spec.add_development_dependency '
|
|
33
|
+
spec.add_development_dependency 'dpl', '~> 1.10.11'
|
|
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'
|
|
32
37
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
|
38
|
+
spec.add_development_dependency 'approvals', '~> 0.0.24'
|
|
39
|
+
spec.add_development_dependency 'appraisal', '~> 2.2.0'
|
|
40
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.7.0'
|
|
33
41
|
spec.add_development_dependency 'simplecov', '~> 0.16.1'
|
|
34
42
|
spec.add_development_dependency 'simplecov-console', '~> 0.4.2'
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
# When updating also update .codeclimate.yml:5
|
|
45
|
+
spec.add_development_dependency 'rubocop', '= 0.71.0'
|
|
37
46
|
spec.add_development_dependency 'guard', '~> 2.15'
|
|
38
47
|
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
|
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'
|
|
39
53
|
|
|
40
54
|
spec.add_development_dependency 'pry'
|
|
41
55
|
spec.add_development_dependency 'pry-nav'
|
|
56
|
+
spec.add_development_dependency 'pry-doc'
|
|
42
57
|
spec.add_development_dependency 'pry-rescue'
|
|
43
58
|
spec.add_development_dependency 'pry-stack_explorer'
|
|
44
59
|
spec.add_development_dependency 'pry-alias'
|
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
|
data/bin/console
CHANGED
|
@@ -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
|
+
}
|