mini_sql 1.1.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d46350915a655a1692de9b775f879609f83ff068bf9b62be6498672b223ee03
4
- data.tar.gz: 192596f2834a676f123afcfb969a32267b311fe20318527ae13b92dd95efc757
3
+ metadata.gz: bbfef3c29b94c4d0bdbb461b52419644f3b042236f58837a6247a84546034e26
4
+ data.tar.gz: 52b175f5f3c712f891f61fe47618584c4279e0bd9f4a0496fb545bd3574eb8ba
5
5
  SHA512:
6
- metadata.gz: 9820695fc27ced3ce61b2a131201c6bb954eca9919cd49f29f2877ebccbbd33ef8e36b4a0284d0a04fde9ec92c1f33bbfe75780515cdec26bdf9493e676b631d
7
- data.tar.gz: d2ba6adbdf07a1a3fb11192ad949c8f632035a357d792eb895350ee3af30f7977d6b8e64d5df59b1338e9349d213e4472099593c6296e39203fac7ee8147da7d
6
+ metadata.gz: 00a003b8bdc6bdf623ad10d3702b3851b0c5bdbc5c1d82c76aa40c5dd797ef11d4bcb153d85cf323d451403d87fb90d0b12c43da3df84a5c49aa84b1d8c8c570
7
+ data.tar.gz: 11fd1b56b47b9ef11d452e1c8eca322ec35ac054a78195fe30bfbeacd1858e67092cc96248f724cab0c9d4b795b0bcdd8e75a3c678bce8ba9787973f47f33439
@@ -4,7 +4,7 @@ on:
4
4
  pull_request:
5
5
  push:
6
6
  branches:
7
- - master
7
+ - main
8
8
 
9
9
  env:
10
10
  PGHOST: localhost
@@ -34,12 +34,14 @@ jobs:
34
34
  - 3306:3306
35
35
  options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
36
36
  strategy:
37
+ fail-fast: false
37
38
  matrix:
38
- ruby: ["2.7", "2.6", "2.5"]
39
+ ruby: ["3.1", "3.0", "2.7"]
39
40
  experimental: [false]
40
41
  include:
41
42
  - ruby: ruby-head
42
43
  experimental: true
44
+ continue-on-error: ${{matrix.experimental}}
43
45
  steps:
44
46
  - uses: actions/checkout@v2
45
47
  - uses: ruby/setup-ruby@v1
@@ -64,3 +66,18 @@ jobs:
64
66
  run: bundle exec rake test
65
67
  - name: Rubocop
66
68
  run: bundle exec rubocop
69
+
70
+ publish:
71
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
72
+ needs: build
73
+ runs-on: ubuntu-latest
74
+
75
+ steps:
76
+ - uses: actions/checkout@v2
77
+
78
+ - name: Release Gem
79
+ uses: discourse/publish-rubygems-action@v2
80
+ env:
81
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
82
+ GIT_EMAIL: team@discourse.org
83
+ GIT_NAME: discoursebot
data/.rubocop.yml CHANGED
@@ -3,6 +3,8 @@ inherit_gem:
3
3
  inherit_mode:
4
4
  merge:
5
5
  - Exclude
6
+ Security/MarshalLoad:
7
+ Enabled: false
6
8
  AllCops:
7
9
  Exclude:
8
10
  - 'bench/**/*'
data/CHANGELOG CHANGED
@@ -1,3 +1,18 @@
1
+ 2022-03-07 - 1.4.0
2
+
3
+ - PERF: Optimize multiple use param in prepared postgres sql
4
+
5
+ 2022-02-02 - 1.3.0
6
+
7
+ - FEATURE: Add ActiveRecordPostgres connection
8
+ This is almost identical to the Postgres connection, but will acquire ActiveRecord's connection lock for each query
9
+
10
+ 2022-01-31 - 1.2.0
11
+
12
+ - Ruby 2.6 is EOL support removed
13
+ - FIX: when multiple params shared a prefix inline encoder may work in unexpected ways
14
+ - FEATURE: add sql_literal for injecting sql in sql builder
15
+
1
16
  2021-03-22 - 1.1.3
2
17
 
3
18
  - DEV: reduce coupling of internal interfaces and allow or cleaner override of prepared connections
data/README.md CHANGED
@@ -77,7 +77,46 @@ builder.query.each do |t|
77
77
  end
78
78
  ```
79
79
 
80
- The builder allows for `order_by`, `where`, `select`, `set`, `limit`, `join`, `left_join` and `offset`.
80
+ The builder predefined next _SQL Literals_
81
+
82
+ | Method | SQL Literal |
83
+ | ------ | ----------- |
84
+ |`select` |`/*select*/`|
85
+ |`where` |`/*where*/`|
86
+ |`where2` |`/*where2*/`|
87
+ |`join` |`/*join*/`|
88
+ |`left_join` |`/*left_join*/`|
89
+ |`group_by` |`/*group_by*/`|
90
+ |`order_by` |`/*order_by*/`|
91
+ |`limit` |`/*limit*/`|
92
+ |`offset` |`/*offset*/`|
93
+ |`set` |`/*set*/`|
94
+
95
+ ### Custom SQL Literals
96
+ Use `sql_literal` for injecting custom sql into Builder
97
+
98
+ ```ruby
99
+ user_builder = conn
100
+ .build("select date_trunc('day', created_at) day, count(*) from user_topics /*where*/")
101
+ .where('type = ?', input_type)
102
+ .group_by("date_trunc('day', created_at)")
103
+
104
+ guest_builder = conn
105
+ .build("select date_trunc('day', created_at) day, count(*) from guest_topics /*where*/")
106
+ .where('state = ?', input_state)
107
+ .group_by("date_trunc('day', created_at)")
108
+
109
+ conn
110
+ .build(<<~SQL)
111
+ with as (/*user*/) u, (/*guest*/) as g
112
+ select COALESCE(g.day, u.day), g.count, u.count
113
+ from u
114
+ /*custom_join*/
115
+ SQL
116
+ .sql_literal(user: user_builder, guest: guest_builder) # builder
117
+ .sql_literal(custom_join: "#{input_cond ? 'FULL' : 'LEFT'} JOIN g on g.day = u.day") # or string
118
+ .query
119
+ ```
81
120
 
82
121
  ## Is it fast?
83
122
  Yes, it is very fast. See benchmarks in [the bench directory](https://github.com/discourse/mini_sql/tree/master/bench).
@@ -202,6 +241,17 @@ builder.where("id IN (?)", ids)
202
241
  builder.prepared(ids.size == 1).query # most frequent query
203
242
  ```
204
243
 
244
+ ## Active Record Postgres
245
+
246
+ When using alongside ActiveRecord, passing in the ActiveRecord connection rather than the raw Postgres connection will allow mini_sql to lock the connection, thereby preventing concurrent use in other threads.
247
+
248
+ ```ruby
249
+ ar_conn = ActiveRecord::Base.connection
250
+ conn = MiniSql::Connection.get(ar_conn)
251
+
252
+ conn.query("select * from topics")
253
+ ```
254
+
205
255
  ## I want more features!
206
256
 
207
257
  MiniSql is designed to be very minimal. Even though the query builder and type materializer give you a lot of mileage, it is not intended to be a fully fledged ORM. If you are looking for an ORM I recommend investigating ActiveRecord or Sequel which provide significantly more features.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module ActiveRecordPostgres
5
+ class Connection < ::MiniSql::Postgres::Connection
6
+ attr_reader :active_record_connection
7
+
8
+ # Initialize a new MiniSql::Postgres::Connection object
9
+ #
10
+ # @param active_record_adapter [ActiveRecord::ConnectionAdapters::PostgresqlAdapter]
11
+ # @param deserializer_cache [MiniSql::DeserializerCache] a cache of field names to deserializer, can be nil
12
+ # @param type_map [PG::TypeMap] a type mapper for all results returned, can be nil
13
+ def initialize(active_record_adapter, args = nil)
14
+ @active_record_connection = active_record_adapter
15
+ super(nil, args)
16
+ end
17
+
18
+ def raw_connection
19
+ active_record_connection.raw_connection
20
+ end
21
+
22
+ # These two methods do not use `run`, so we need to apply
23
+ # the lock separately:
24
+ def query_each(sql, *params)
25
+ with_lock { super }
26
+ end
27
+ def query_each_hash(sql, *params)
28
+ with_lock { super }
29
+ end
30
+
31
+ private
32
+
33
+ def with_lock
34
+ active_record_connection.lock.synchronize { yield }
35
+ end
36
+
37
+ def run(sql, params)
38
+ with_lock { super }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -11,32 +11,46 @@ class MiniSql::Builder
11
11
  @is_prepared = false
12
12
  end
13
13
 
14
- [:set, :where2, :where, :order_by, :left_join, :join, :select, :group_by].each do |k|
15
- define_method k do |sql_part, *args|
16
- if Hash === args[0]
17
- @args.merge!(args[0])
18
- else # convert simple params to hash
19
- args.each do |v|
20
- # for compatability with AR param encoded we keep a non _
21
- # prefix (must be [a-z])
22
- param = "mq_auto_#{@count_variables += 1}"
23
- sql_part = sql_part.sub('?', ":#{param}")
24
- @args[param.to_sym] = v
14
+ literals1 =
15
+ [:set, :where2, :where, :order_by, :left_join, :join, :select, :group_by].each do |k|
16
+ define_method k do |sql_part, *args|
17
+ if Hash === args[0]
18
+ @args.merge!(args[0])
19
+ else # convert simple params to hash
20
+ args.each do |v|
21
+ # for compatability with AR param encoded we keep a non _
22
+ # prefix (must be [a-z])
23
+ param = "mq_auto_#{@count_variables += 1}"
24
+ sql_part = sql_part.sub('?', ":#{param}")
25
+ @args[param.to_sym] = v
26
+ end
25
27
  end
28
+
29
+ @sections[k] ||= []
30
+ @sections[k] << sql_part
31
+ self
26
32
  end
33
+ end
27
34
 
28
- @sections[k] ||= []
29
- @sections[k] << sql_part
30
- self
35
+ literals2 =
36
+ [:limit, :offset].each do |k|
37
+ define_method k do |value|
38
+ @args["mq_auto_#{k}".to_sym] = value
39
+ @sections[k] = true
40
+ self
41
+ end
31
42
  end
32
- end
33
43
 
34
- [:limit, :offset].each do |k|
35
- define_method k do |value|
36
- @args["mq_auto_#{k}".to_sym] = value
37
- @sections[k] = true
38
- self
44
+ PREDEFINED_SQL_LITERALS = (literals1 | literals2).to_set
45
+
46
+ def sql_literal(literals)
47
+ literals.each do |name, part_sql|
48
+ if PREDEFINED_SQL_LITERALS.include?(name)
49
+ raise "/*#{name}*/ is predefined, use method `.#{name}` instead `sql_literal`"
50
+ end
51
+ @sections[name] = part_sql.is_a?(::MiniSql::Builder) ? part_sql.to_sql : part_sql
39
52
  end
53
+ self
40
54
  end
41
55
 
42
56
  [:query, :query_single, :query_hash, :query_array, :exec].each do |m|
@@ -93,9 +107,13 @@ class MiniSql::Builder
93
107
  joined = (+"GROUP BY ") << v.join(" , ")
94
108
  when :set
95
109
  joined = (+"SET ") << v.join(" , ")
110
+ else # for sql_literal
111
+ joined = v
96
112
  end
97
113
 
98
- sql.sub!("/*#{k}*/", joined)
114
+ unless sql.sub!("/*#{k}*/", joined)
115
+ raise "The section for the /*#{k}*/ clause was not found!"
116
+ end
99
117
  end
100
118
 
101
119
  sql
@@ -6,6 +6,8 @@ module MiniSql
6
6
  def self.get(raw_connection, options = {})
7
7
  if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
8
8
  Postgres::Connection.new(raw_connection, options)
9
+ elsif (defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) && (ActiveRecord::ConnectionAdapters::PostgreSQLAdapter === raw_connection)
10
+ ActiveRecordPostgres::Connection.new(raw_connection, options)
9
11
  elsif (defined? ::ArJdbc)
10
12
  Postgres::Connection.new(raw_connection, options)
11
13
  elsif (defined? ::SQLite3::Database) && (SQLite3::Database === raw_connection)
@@ -20,7 +20,11 @@ module MiniSql
20
20
  def encode_hash(sql, hash)
21
21
  sql = sql.dup
22
22
 
23
- hash.each do |k, v|
23
+ # longest key first for gsub to work
24
+ # in an expected way
25
+ hash.sort do |(k, _), (k1, _)|
26
+ k1.to_s.length <=> k.to_s.length
27
+ end.each do |k, v|
24
28
  sql.gsub!(":#{k}") do
25
29
  # ignore ::int and stuff like that
26
30
  # $` is previous to match
@@ -58,10 +62,7 @@ module MiniSql
58
62
  when false then "false"
59
63
  when nil then "NULL"
60
64
  when [] then "NULL"
61
- when Array
62
- value.map do |v|
63
- quote_val(v)
64
- end.join(', ')
65
+ when Array then value.map { |v| quote_val(v) }.join(', ')
65
66
  else raise TypeError, "can't quote #{value.class.name}"
66
67
  end
67
68
  end
@@ -6,6 +6,33 @@ module MiniSql
6
6
  module Postgres
7
7
  class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
8
 
9
+ def bind_hash(sql, hash)
10
+ sql = sql.dup
11
+ binds = []
12
+ bind_names = []
13
+ i = 0
14
+
15
+ hash.each do |k, v|
16
+ bind_outputs =
17
+ array_wrap(v).map { |vv|
18
+ binds << vv
19
+ bind_names << [BindName.new(k)]
20
+ bind_output(i += 1)
21
+ }.join(', ')
22
+
23
+ sql.gsub!(":#{k}") do
24
+ # ignore ::int and stuff like that
25
+ # $` is previous to match
26
+ if $` && $`[-1] != ":"
27
+ bind_outputs
28
+ else
29
+ ":#{k}"
30
+ end
31
+ end
32
+ end
33
+ [sql, binds, bind_names]
34
+ end
35
+
9
36
  def bind_output(i)
10
37
  "$#{i}"
11
38
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module MiniSql
3
- VERSION = "1.1.3"
3
+ VERSION = "1.4.0"
4
4
  end
data/lib/mini_sql.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  # we need this for a coder
4
4
  require "bigdecimal"
5
5
 
6
+ # used for builder
7
+ require "set"
8
+
6
9
  require_relative "mini_sql/version"
7
10
  require_relative "mini_sql/connection"
8
11
  require_relative "mini_sql/deserializer_cache"
@@ -28,6 +31,10 @@ module MiniSql
28
31
  autoload :PreparedBinds, "mini_sql/postgres/prepared_binds"
29
32
  end
30
33
 
34
+ module ActiveRecordPostgres
35
+ autoload :Connection, "mini_sql/active_record_postgres/connection"
36
+ end
37
+
31
38
  module Sqlite
32
39
  autoload :Connection, "mini_sql/sqlite/connection"
33
40
  autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
data/mini_sql.gemspec CHANGED
@@ -35,12 +35,12 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency "bundler", "> 1.16"
36
36
  spec.add_development_dependency "rake", "> 10"
37
37
  spec.add_development_dependency "minitest", "~> 5.0"
38
- spec.add_development_dependency "guard", "~> 2.14"
38
+ spec.add_development_dependency "guard", "~> 2.18"
39
39
  spec.add_development_dependency "guard-minitest", "~> 2.4"
40
- spec.add_development_dependency "activesupport", "~> 5.2"
41
- spec.add_development_dependency 'rubocop', '~> 1.4.0'
42
- spec.add_development_dependency 'rubocop-discourse', '~> 2.4.1'
43
- spec.add_development_dependency 'm', '~> 1.5.1'
40
+ spec.add_development_dependency "activesupport", "~> 7.0"
41
+ spec.add_development_dependency 'rubocop', '~> 1.25.0'
42
+ spec.add_development_dependency 'rubocop-discourse', '~> 2.5.0'
43
+ spec.add_development_dependency 'm', '~> 1.6.0'
44
44
 
45
45
  if RUBY_ENGINE == 'jruby'
46
46
  spec.add_development_dependency "activerecord-jdbcpostgresql-adapter", "~> 52.2"
@@ -48,5 +48,6 @@ Gem::Specification.new do |spec|
48
48
  spec.add_development_dependency "pg", "> 1"
49
49
  spec.add_development_dependency "mysql2"
50
50
  spec.add_development_dependency "sqlite3", "~> 1.3"
51
+ spec.add_development_dependency "activerecord", "~> 7.0.0"
51
52
  end
52
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-23 00:00:00.000000000 Z
11
+ date: 2022-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.14'
61
+ version: '2.18'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.14'
68
+ version: '2.18'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: guard-minitest
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,56 +86,56 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '5.2'
89
+ version: '7.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '5.2'
96
+ version: '7.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rubocop
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.4.0
103
+ version: 1.25.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.4.0
110
+ version: 1.25.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop-discourse
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 2.4.1
117
+ version: 2.5.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 2.4.1
124
+ version: 2.5.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: m
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 1.5.1
131
+ version: 1.6.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 1.5.1
138
+ version: 1.6.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: pg
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +178,20 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '1.3'
181
+ - !ruby/object:Gem::Dependency
182
+ name: activerecord
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 7.0.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 7.0.0
181
195
  description: A fast, safe, simple direct SQL executor for PG
182
196
  email:
183
197
  - sam.saffron@gmail.com
@@ -210,6 +224,7 @@ files:
210
224
  - lib/mini_sql.rb
211
225
  - lib/mini_sql/abstract/prepared_binds.rb
212
226
  - lib/mini_sql/abstract/prepared_cache.rb
227
+ - lib/mini_sql/active_record_postgres/connection.rb
213
228
  - lib/mini_sql/builder.rb
214
229
  - lib/mini_sql/connection.rb
215
230
  - lib/mini_sql/decoratable.rb
@@ -244,7 +259,7 @@ metadata:
244
259
  bug_tracker_uri: https://github.com/discourse/mini_sql/issues
245
260
  source_code_uri: https://github.com/discourse/mini_sql
246
261
  changelog_uri: https://github.com/discourse/mini_sql/blob/master/CHANGELOG
247
- post_install_message:
262
+ post_install_message:
248
263
  rdoc_options: []
249
264
  require_paths:
250
265
  - lib
@@ -259,8 +274,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
274
  - !ruby/object:Gem::Version
260
275
  version: '0'
261
276
  requirements: []
262
- rubygems_version: 3.2.2
263
- signing_key:
277
+ rubygems_version: 3.1.6
278
+ signing_key:
264
279
  specification_version: 4
265
280
  summary: A fast, safe, simple direct SQL executor
266
281
  test_files: []