pg_exec_array_params 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []