pg_exec_array_params 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 487eaa0af43d806c4d5ead7fe7ba640a23f0f382611773ed79565c583740fb5c
4
+ data.tar.gz: e6b1b0c0347546658a9786642a19f77acd173d1413c9174165e4ec12f312e105
5
+ SHA512:
6
+ metadata.gz: c7f70ef196f4353edf9195224598e83b82be4e5811ae2610b631bf416ccdceac89cdedf133aa96ad1c706e1e6994dbf074d171584fec9bbff91e61775b3c613a
7
+ data.tar.gz: 4410043f791594bc5367ffc7c94d75ab56bfd5cfeaf84076b2745ae27854d56645aec7d6eea5a7bf1984f6c16d373b1da1ec5e25c13cf360d33ffd9c4bbe26f2
@@ -0,0 +1,50 @@
1
+ name: branch
2
+ on:
3
+ push:
4
+ branches-ignore:
5
+ - master
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ services:
10
+ postgres:
11
+ image: postgres:13
12
+ env:
13
+ POSTGRES_USER: postgres
14
+ POSTGRES_PASSWORD: postgres
15
+ POSTGRES_DB: pg_exec_array_params_test
16
+ ports: ['5432:5432']
17
+ options: >-
18
+ --health-cmd pg_isready
19
+ --health-interval 10s
20
+ --health-timeout 5s
21
+ --health-retries 5
22
+ strategy:
23
+ matrix:
24
+ ruby: [ '2.7' ]
25
+ pg: [ '>= 1' ]
26
+ name: Ruby ${{ matrix.ruby }}, pg ${{ matrix.pg }}
27
+ steps:
28
+ - uses: actions/checkout@v2
29
+ - uses: actions/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby }}
32
+ - uses: actions/cache@v2
33
+ with:
34
+ path: vendor/bundle
35
+ key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.pg }}-${{ hashFiles('**/Gemfile') }}
36
+ restore-keys: |
37
+ ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.pg }}
38
+ - name: Test
39
+ env:
40
+ POSTGRES_USER: postgres
41
+ POSTGRES_PASSWORD: postgres
42
+ POSTGRES_DB: pg_exec_array_params_test
43
+ PG: ${{ matrix.pg }}
44
+ PG_GEM_VERSION: ${{ matrix.pg }}
45
+ CI: true
46
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
47
+ run: |
48
+ bundle config path vendor/bundle
49
+ bundle install --jobs 4 --retry 3 --without benchmark
50
+ bundle exec rake ci
@@ -0,0 +1,55 @@
1
+ name: ci
2
+ on:
3
+ pull_request:
4
+ types:
5
+ - opened
6
+ - synchronize
7
+ - reopened
8
+ push:
9
+ branches:
10
+ - master
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ services:
15
+ postgres:
16
+ image: postgres:13
17
+ env:
18
+ POSTGRES_USER: postgres
19
+ POSTGRES_PASSWORD: postgres
20
+ POSTGRES_DB: pg_exec_array_params_test
21
+ ports: ['5432:5432']
22
+ options: >-
23
+ --health-cmd pg_isready
24
+ --health-interval 10s
25
+ --health-timeout 5s
26
+ --health-retries 5
27
+ strategy:
28
+ matrix:
29
+ ruby: [ '2.5', '2.6', '2.7' ]
30
+ pg: [ '< 1', '>= 1' ]
31
+ name: Ruby ${{ matrix.ruby }}, pg ${{ matrix.pg }}
32
+ steps:
33
+ - uses: actions/checkout@v2
34
+ - uses: actions/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby }}
37
+ - uses: actions/cache@v2
38
+ with:
39
+ path: vendor/bundle
40
+ key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.pg }}-${{ hashFiles('**/Gemfile') }}
41
+ restore-keys: |
42
+ ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.pg }}
43
+ - name: Test
44
+ env:
45
+ POSTGRES_USER: postgres
46
+ POSTGRES_PASSWORD: postgres
47
+ POSTGRES_DB: pg_exec_array_params_test
48
+ PG: ${{ matrix.pg }}
49
+ PG_GEM_VERSION: ${{ matrix.pg }}
50
+ CI: true
51
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
52
+ run: |
53
+ bundle config path vendor/bundle
54
+ bundle install --jobs 4 --retry 3 --without benchmark
55
+ bundle exec rake ci
@@ -0,0 +1,6 @@
1
+ Gemfile.lock
2
+ .rspec_status
3
+ .bundle
4
+ .rspec
5
+ bin
6
+ coverage/
@@ -0,0 +1,11 @@
1
+ Metrics/AbcSize:
2
+ Max: 30
3
+
4
+ Metrics/MethodLength:
5
+ Max: 30
6
+
7
+ Metrics/BlockLength:
8
+ Enabled: false
9
+
10
+ Style/Documentation:
11
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in pg_exec_array_params.gemspec
6
+ gemspec
7
+
8
+ gem 'codecov', require: false
9
+ gem 'rubocop', '~> 1.0'
10
+ gem 'rubocop-rspec', '~> 2.0.0.pre'
11
+ gem 'rspec-github', '~> 2.3'
12
+ gem 'pry-byebug', '~> 3.9'
13
+ gem 'simplecov', '~> 0.19'
14
+
15
+ group :benchmark do
16
+ gem 'activerecord', require: false
17
+ gem 'benchmark-ips', require: false
18
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020 Vlad Bokov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ # PgExecArrayParams
2
+
3
+ ![](https://github.com/lunatic-cat/pg_exec_array_params/workflows/ci/badge.svg)
4
+
5
+ Use same parametized query and put `Array<T>` instead of any `T`
6
+
7
+ ## Example
8
+
9
+ ```ruby
10
+ query = 'select * from t1 where a1 = $1 and a2 = $2 and a3 = $3 and a4 = $4'
11
+ params = [1, [2, 3], 'foo', ['bar', 'baz']]
12
+
13
+ # PG::Connection.exec_params called with:
14
+ # 'SELECT * FROM "t1" WHERE "a1" = $1 AND "a2" IN ($2, $3) AND "a3" = $4 AND "a4" IN ($5, $6)'
15
+ # [1, 2, 3, "foo", "bar", "baz"]
16
+ PgExecArrayParams.exec_array_params(conn, query, params)
17
+ ```
18
+
19
+ ## Problem
20
+
21
+ ```ruby
22
+ conn.exec_params('select * from users where id IN ($1)', [1,2])
23
+ => PG::IndeterminateDatatype: ERROR: could not determine data type of parameter $2
24
+
25
+ conn.exec_params('select * from users where id IN ($1)', [[1,2]])
26
+ => PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer: "[1, 2]"
27
+ ```
28
+
29
+ Currently you would generate `$n` parts and flatten params.
30
+ Or you can inline and embed arrays into query. *Don't forget to escape them*
31
+
32
+ ## Solution
33
+
34
+ This library encapsulates the first approach in a clean way:
35
+
36
+ ```ruby
37
+ # rewrite query under the hood to
38
+ # select * from users where id IN ($1, $2)
39
+ PgExecArrayParams.exec_array_params(conn, 'select * from users where id = $1', [[1,2]])
40
+ => [{"id" => 1}, {"id" => 2}]
41
+ ```
42
+
43
+ ## Integration with 'pg' gem
44
+
45
+ ```ruby
46
+ PG::Connection.include(PgExecArrayParams) # once in initializer
47
+
48
+ conn.exec_array_params('select * from users where id = $1', [[1,2]])
49
+ => [{"id" => 1}, {"id" => 2}]
50
+ ```
51
+
52
+ ## Rails note
53
+
54
+ `ActiveRecord` uses the second path (inline + escape).
55
+
56
+ ```ruby
57
+ User.where(age: ["1'; drop table users;", "2"]).to_sql
58
+ => SELECT "users".* FROM "users" WHERE "users"."age" IN ('1''; drop table users;', '2')
59
+ ```
60
+
61
+ It's solid and bulletproof, but
62
+
63
+ - it must support multiple databases, but non-trivial queries require raw sql chunks anyway
64
+ - it's clever, but not so fast as raw `pg`
65
+ - if you're using `AR::Relation#to_sql` just to handle arrays, consider using this
66
+
67
+ ## Benchmark
68
+
69
+ ```sh
70
+ BENCH_PG_URL='postgres://...' bundle exec ruby benchmark.rb
71
+ ```
72
+
73
+ <details>
74
+ <summary>Benchmarking SQL generation</summary>
75
+
76
+ ```
77
+ Warming up --------------------------------------
78
+ activerecord 1.070k i/100ms
79
+ exec_array_params 213.704k i/100ms
80
+ Calculating -------------------------------------
81
+ activerecord 11.359k (± 3.9%) i/s - 56.710k in 5.000406s
82
+ exec_array_params 2.151M (± 3.0%) i/s - 10.899M in 5.072579s
83
+ ```
84
+ </details>
85
+
86
+ ```
87
+ Comparison:
88
+ exec_array_params: 2150601.0 i/s
89
+ activerecord: 11359.0 i/s - 189.33x (± 0.00) slower
90
+ ```
91
+
92
+ <details>
93
+ <summary>Benchmarking query</summary>
94
+
95
+ ```
96
+ Warming up --------------------------------------
97
+ activerecord#to_a 1.000 i/100ms
98
+ activerecord#pluck 1.000 i/100ms
99
+ exec_array_params 2.000 i/100ms
100
+ pg 2.000 i/100ms
101
+ Calculating -------------------------------------
102
+ activerecord#to_a 4.429 (± 0.0%) i/s - 23.000 in 5.203405s
103
+ activerecord#pluck 18.889 (± 5.3%) i/s - 95.000 in 5.044102s
104
+ exec_array_params 25.093 (± 4.0%) i/s - 126.000 in 5.039405s
105
+ pg 23.632 (± 8.5%) i/s - 118.000 in 5.033961s
106
+ ```
107
+ </details>
108
+
109
+ ```
110
+ Comparison:
111
+ exec_array_params: 25.1 i/s
112
+ pg: 23.6 i/s - same-ish: difference falls within error
113
+ activerecord#pluck: 18.9 i/s - 1.33x (± 0.00) slower
114
+ activerecord#to_a: 4.4 i/s - 5.67x (± 0.00) slower
115
+ ```
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ RSpec::Core::RakeTask.new(:spec_github) do |t|
11
+ t.rspec_opts = '--format RSpec::Github::Formatter -f progress'
12
+ end
13
+
14
+ task ci: %i[rubocop spec_github]
15
+ task default: %i[rubocop spec]
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark/ips'
4
+ require 'active_record'
5
+ require 'pg'
6
+
7
+ $LOAD_PATH.unshift(File.expand_path('lib', __dir__))
8
+ require 'pg_exec_array_params'
9
+
10
+ class User < ActiveRecord::Base
11
+ end
12
+
13
+ def connect(url)
14
+ # ActiveRecord::Base.legacy_connection_handling = false
15
+ ActiveRecord::Base.logger = Logger.new(IO::NULL)
16
+ ActiveRecord::Base.configurations = { default_env: { adapter: 'postgresql', url: url, pool: 1 } }
17
+ ActiveRecord::Base.establish_connection
18
+ ActiveRecord::Migration.verbose = false
19
+ ActiveRecord::Schema.define(version: 1) do
20
+ create_table :users, if_not_exists: true do |t|
21
+ t.integer :age
22
+ end
23
+ add_index(:users, :age) unless index_exists?(:users, :age)
24
+ end
25
+ ActiveRecord::Base.connection.raw_connection
26
+ end
27
+
28
+ def sql
29
+ puts 'Benchmarking SQL generation'
30
+
31
+ connect(ENV.fetch('BENCH_PG_URL'))
32
+ params = ["1'; drop table users;", '2']
33
+ query = 'select * from users where age = $1'
34
+
35
+ Benchmark.ips do |x|
36
+ x.report('activerecord') { User.where(age: params).to_sql }
37
+ x.report('exec_array_params') { PgExecArrayParams::Query.new(query, params).sql }
38
+ x.compare!
39
+ end
40
+ end
41
+
42
+ def query
43
+ puts 'Benchmarking query'
44
+
45
+ conn = connect(ENV.fetch('BENCH_PG_URL'))
46
+ PG::Connection.include(PgExecArrayParams)
47
+ if conn.exec('select count(*) from users').first['count'].to_i < 1_000_000
48
+ puts "Seed #{conn.exec('INSERT INTO users (age) SELECT generate_series(1,1500000) % 90;')}"
49
+ end
50
+
51
+ query = 'select * from users where age IN ($1, $2, $3)'
52
+ params = [10, 20, 30]
53
+ query2 = 'select * from users where age = $1'
54
+ params2 = [[10, 20, 30]]
55
+
56
+ Benchmark.ips do |x|
57
+ x.report('activerecord#to_a') { User.where(age: params).to_a }
58
+ x.report('activerecord#pluck') { User.where(age: params).pluck(:id, :age) }
59
+ x.report('exec_array_params') { conn.exec_array_params(query2, params2).to_a }
60
+ x.report('pg') { conn.exec_params(query, params).to_a }
61
+ x.compare!
62
+ end
63
+ end
64
+
65
+ if __FILE__ == $PROGRAM_NAME
66
+ if (meth = ARGV.first)
67
+ send meth
68
+ else
69
+ sql && query
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pg_exec_array_params/query'
4
+ require 'pg_exec_array_params/version'
5
+
6
+ module PgExecArrayParams
7
+ class Error < StandardError; end
8
+ module_function
9
+
10
+ def exec_array_params(conn, sql, params, *args)
11
+ Query.new(sql, params).exec_params(conn, *args)
12
+ end
13
+
14
+ def self.included(base)
15
+ return unless base.name == 'PG::Connection'
16
+
17
+ base.define_method :exec_array_params do |sql, params, *args|
18
+ Query.new(sql, params).exec_params(self, *args)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pg_query'
4
+
5
+ module PgExecArrayParams
6
+ class Query
7
+ PARAM_REF = 'ParamRef'
8
+ REXPR = 'rexpr'
9
+ A_EXPR = 'A_Expr'
10
+ KIND = 'kind'
11
+ LOCATION = 'location'
12
+ NUMBER = 'number'
13
+
14
+ EQ_KIND = 0
15
+ IN_KIND = 7
16
+
17
+ attr_reader :query, :args
18
+
19
+ def initialize(query, args = [])
20
+ @query = query
21
+ @args = args
22
+ end
23
+
24
+ def exec_params(conn, *args)
25
+ conn.exec_params(sql, binds, *args)
26
+ end
27
+
28
+ def sql
29
+ return query unless should_rebuild?
30
+
31
+ @sql || (rebuild_query! && @sql)
32
+ end
33
+
34
+ def binds
35
+ return args unless should_rebuild?
36
+
37
+ @binds || (rebuild_query! && @binds)
38
+ end
39
+
40
+ private
41
+
42
+ def should_rebuild?
43
+ args.any? { |param| param.is_a?(Array) }
44
+ end
45
+
46
+ def rebuild_query!
47
+ @param_idx = 0
48
+ @ref_idx = 1
49
+ @binds = []
50
+ each_param_ref do |value|
51
+ # puts({value_before: value}.inspect)
52
+
53
+ if args[@param_idx].is_a? Array
54
+ value[KIND] = IN_KIND
55
+ value[REXPR] = []
56
+ args[@param_idx].each do |param|
57
+ raise Error, "Param: #{param.inspect} not primitive" if param.respond_to?(:each)
58
+
59
+ value[REXPR] << { PARAM_REF => { NUMBER => @ref_idx } }
60
+ @binds << param
61
+ @ref_idx += 1
62
+ end
63
+ else
64
+ value[REXPR][PARAM_REF][NUMBER] = @ref_idx
65
+ @ref_idx += 1
66
+
67
+ # nested_refs == 1 unwraps, wrap it back
68
+ value[REXPR] = [value[REXPR]] if value[KIND] == IN_KIND
69
+
70
+ @binds << args[@param_idx]
71
+ end
72
+
73
+ @param_idx += 1
74
+ # puts({value_after_: value}.inspect)
75
+ end
76
+ @sql = tree.deparse
77
+ # puts({sql: @sql, binds: @binds}.inspect)
78
+ true
79
+ end
80
+
81
+ def tree
82
+ @tree ||= PgQuery.parse(query)
83
+ end
84
+
85
+ def each_param_ref
86
+ tree.send :treewalker!, tree.tree do |_expr, key, value, _location|
87
+ if key == A_EXPR
88
+ if assign_param_via_eq?(value)
89
+ yield value
90
+ elsif (nested_refs = assign_param_via_in?(value))
91
+ if nested_refs == 1
92
+ value[REXPR] = value[REXPR].first
93
+ yield value
94
+ else
95
+ message = [
96
+ 'Cannot splice multiple references, leave the only one:',
97
+ query,
98
+ refs_underline(value)
99
+ ].join("\n")
100
+ raise Error, message
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def refs_underline(value)
108
+ from, size = refs_at(value)
109
+ "#{'^'.rjust(from, ' ')}#{'-'.rjust(size, '-')}^"
110
+ end
111
+
112
+ def refs_at(value)
113
+ first_ref = value[REXPR].find { |vexpr| vexpr.key?(PARAM_REF) } [PARAM_REF]
114
+ last_ref = value[REXPR].reverse.find { |vexpr| vexpr.key?(PARAM_REF) } [PARAM_REF]
115
+ started = first_ref[LOCATION] + 1
116
+ ended = last_ref[LOCATION] + last_ref[NUMBER].to_s.size
117
+ [started, ended - started]
118
+ end
119
+
120
+ # = $1
121
+ # {"kind"=>0, "name"=>[{"String"=>{"str"=>"="}}],
122
+ # "lexpr"=>{"ColumnRef"=>{"fields"=>[{"String"=>{"str"=>"companies"}}, {"String"=>{"str"=>"id"}}],
123
+ # "location"=>1242}},
124
+ # "rexpr"=>{"ParamRef"=>{"number"=>4, "location"=>1261}}, "location"=>1259}
125
+ def assign_param_via_eq?(value)
126
+ (value[KIND] == EQ_KIND) && value[REXPR].is_a?(Hash) && value[REXPR].key?(PARAM_REF)
127
+ end
128
+
129
+ # IN ($1), returns number of nested REFs
130
+ def assign_param_via_in?(value)
131
+ (value[KIND] == IN_KIND) && value[REXPR].is_a?(Array) && value[REXPR].count { |vexpr| vexpr.key?(PARAM_REF) }
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgExecArrayParams
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/pg_exec_array_params/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'pg_exec_array_params'
7
+ spec.version = PgExecArrayParams::VERSION
8
+ spec.authors = ['Vlad Bokov']
9
+ spec.email = ['vlad@lunatic.cat']
10
+ spec.license = 'MIT'
11
+
12
+ spec.summary = 'PG::Connection#exec_params with arrays'
13
+ spec.description = 'Escape each array element inside PG::Connection#exec_params properly'
14
+ spec.homepage = 'https://github.com/lunatic-cat/pg_exec_array_params'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/lunatic-cat/pg_exec_array_params'
19
+
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency('pg_query', '~> 1.2')
26
+
27
+ spec.add_development_dependency('pg', ENV.fetch('PG_GEM_VERSION', '~> 0'))
28
+ spec.add_development_dependency('rspec', '~> 3.0')
29
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_exec_array_params
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vlad Bokov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg_query
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Escape each array element inside PG::Connection#exec_params properly
56
+ email:
57
+ - vlad@lunatic.cat
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".github/workflows/branch.yml"
63
+ - ".github/workflows/ci.yml"
64
+ - ".gitignore"
65
+ - ".rubocop.yml"
66
+ - Gemfile
67
+ - LICENSE
68
+ - README.md
69
+ - Rakefile
70
+ - benchmark.rb
71
+ - lib/pg_exec_array_params.rb
72
+ - lib/pg_exec_array_params/query.rb
73
+ - lib/pg_exec_array_params/version.rb
74
+ - pg_exec_array_params.gemspec
75
+ homepage: https://github.com/lunatic-cat/pg_exec_array_params
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ homepage_uri: https://github.com/lunatic-cat/pg_exec_array_params
80
+ source_code_uri: https://github.com/lunatic-cat/pg_exec_array_params
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.4.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.1.2
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: PG::Connection#exec_params with arrays
100
+ test_files: []