mini_sql 1.1.0 → 1.2.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: 7ce3e8b5fee8c39506de8439826252434ddce6249287bb3a74c411a41a5e5726
4
- data.tar.gz: cbab24b31bbf55e39cb824224e694e117b42c30e20189f64ffd2163cab3e12b6
3
+ metadata.gz: fc8d066cc7257d5601c5b75139172b58bb4ad7adc0b992d1ed9aa239fa8a9c55
4
+ data.tar.gz: 68b1a4714bb682c5cf49db9a2d6c70834dbce349cb04db949cb50c1cac73cd76
5
5
  SHA512:
6
- metadata.gz: f01c57922c3ead43474e02585c365b914880ef6924f7acce041e2ee0e984217eadf4aa25f81a0c0c79ee2b9de7235311a2d7155be1a512f2f545d6fbe81ab6e5
7
- data.tar.gz: 47de031da6a0efaeded3981fa2eadefd4a5261cbef73f2fcfb998dd1e671c6c19015a2b97ef70d7311d847558934f8ec8fe6678f2d665cc8ad6dffe6d10a879d
6
+ metadata.gz: dc1ca8c2a28d0693bf691592d57b8cdc103461707823c1e113e54c17c067ccf2e35bc7666880f2d939701c68bf28588757dc48553222e411b67da2279aaa2384
7
+ data.tar.gz: 67419648795ff1999fe7ddef8dbf42e4e91dbb86eb27fa2d824ee8f1576647ef08b7b73570320870911633528ff9dfebbd9ed2fda16550f97defba6476ea418f
@@ -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,21 @@
1
+ 2022-31-01 - 1.2.0
2
+
3
+ - Ruby 2.6 is EOL support removed
4
+ - FIX: when multiple params shared a prefix inline encoder may work in unexpected ways
5
+ - FEATURE: add sql_literal for injecting sql in sql builder
6
+
7
+ 2021-03-22 - 1.1.3
8
+
9
+ - DEV: reduce coupling of internal interfaces and allow or cleaner override of prepared connections
10
+
11
+ 2021-03-22 - 1.1.2
12
+
13
+ - FEATURE: improve compatability with clients overriding raw_connection
14
+
15
+ 2021-03-22 - 1.1.1
16
+
17
+ - FIX: compatability with ActiveRecord param encoder
18
+
1
19
  2021-03-22 - 1.1.0
2
20
 
3
21
  - FEATURE: added new APIs to support prepared statements
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).
@@ -11,30 +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
- param = "_m_#{@count_variables += 1}"
21
- sql_part = sql_part.sub('?', ":#{param}")
22
- @args[param] = 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
23
27
  end
28
+
29
+ @sections[k] ||= []
30
+ @sections[k] << sql_part
31
+ self
24
32
  end
33
+ end
25
34
 
26
- @sections[k] ||= []
27
- @sections[k] << sql_part
28
- 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
29
42
  end
30
- end
31
43
 
32
- [:limit, :offset].each do |k|
33
- define_method k do |value|
34
- @args["_m_#{k}"] = value
35
- @sections[k] = true
36
- 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
37
52
  end
53
+ self
38
54
  end
39
55
 
40
56
  [:query, :query_single, :query_hash, :query_array, :exec].each do |m|
@@ -82,18 +98,22 @@ class MiniSql::Builder
82
98
  when :left_join
83
99
  joined = v.map { |item| (+"LEFT JOIN ") << item }.join("\n")
84
100
  when :limit
85
- joined = (+"LIMIT :_m_limit")
101
+ joined = (+"LIMIT :mq_auto_limit")
86
102
  when :offset
87
- joined = (+"OFFSET :_m_offset")
103
+ joined = (+"OFFSET :mq_auto_offset")
88
104
  when :order_by
89
105
  joined = (+"ORDER BY ") << v.join(" , ")
90
106
  when :group_by
91
107
  joined = (+"GROUP BY ") << v.join(" , ")
92
108
  when :set
93
109
  joined = (+"SET ") << v.join(" , ")
110
+ else # for sql_literal
111
+ joined = v
94
112
  end
95
113
 
96
- sql.sub!("/*#{k}*/", joined)
114
+ unless sql.sub!("/*#{k}*/", joined)
115
+ raise "The section for the /*#{k}*/ clause was not found!"
116
+ end
97
117
  end
98
118
 
99
119
  sql
@@ -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
@@ -9,12 +9,14 @@ module MiniSql
9
9
  @raw_connection = raw_connection
10
10
  @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
11
11
  @deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
12
-
13
- @prepared = PreparedConnection.new(self, @deserializer_cache)
14
12
  end
15
13
 
16
14
  def prepared(condition = true)
17
- condition ? @prepared : self
15
+ if condition
16
+ @prepared ||= PreparedConnection.new(self)
17
+ else
18
+ self
19
+ end
18
20
  end
19
21
 
20
22
  def query_single(sql, *params)
@@ -37,12 +39,12 @@ module MiniSql
37
39
 
38
40
  def query(sql, *params)
39
41
  result = run(sql, :array, params)
40
- @deserializer_cache.materialize(result)
42
+ deserializer_cache.materialize(result)
41
43
  end
42
44
 
43
45
  def query_decorator(decorator, sql, *params)
44
46
  result = run(sql, :array, params)
45
- @deserializer_cache.materialize(result, decorator)
47
+ deserializer_cache.materialize(result, decorator)
46
48
  end
47
49
 
48
50
  def escape_string(str)
@@ -6,10 +6,9 @@ module MiniSql
6
6
 
7
7
  attr_reader :unprepared
8
8
 
9
- def initialize(unprepared_connection, deserializer_cache)
9
+ def initialize(unprepared_connection)
10
10
  @unprepared = unprepared_connection
11
11
  @raw_connection = unprepared_connection.raw_connection
12
- @deserializer_cache = deserializer_cache
13
12
  @param_encoder = unprepared_connection.param_encoder
14
13
 
15
14
  @prepared_cache = PreparedCache.new(@raw_connection)
@@ -24,6 +23,10 @@ module MiniSql
24
23
  condition ? self : @unprepared
25
24
  end
26
25
 
26
+ def deserializer_cache
27
+ @unprepared.deserializer_cache
28
+ end
29
+
27
30
  private def run(sql, as, params)
28
31
  prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
29
32
  statement = @prepared_cache.prepare_statement(prepared_sql)
@@ -3,7 +3,7 @@
3
3
  module MiniSql
4
4
  module Postgres
5
5
  class Connection < MiniSql::Connection
6
- attr_reader :raw_connection, :type_map, :param_encoder
6
+ attr_reader :raw_connection, :param_encoder, :deserializer_cache
7
7
 
8
8
  def self.default_deserializer_cache
9
9
  @deserializer_cache ||= DeserializerCache.new
@@ -40,8 +40,6 @@ module MiniSql
40
40
  @deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
41
41
  @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
42
42
  @type_map = args && args[:type_map]
43
-
44
- @prepared = PreparedConnection.new(self, @deserializer_cache)
45
43
  end
46
44
 
47
45
  def type_map
@@ -49,7 +47,11 @@ module MiniSql
49
47
  end
50
48
 
51
49
  def prepared(condition = true)
52
- condition ? @prepared : self
50
+ if condition
51
+ @prepared ||= PreparedConnection.new(self)
52
+ else
53
+ self
54
+ end
53
55
  end
54
56
 
55
57
  # Returns a flat array containing all results.
@@ -96,7 +98,7 @@ module MiniSql
96
98
  def query(sql, *params)
97
99
  result = run(sql, params)
98
100
  result.type_map = type_map
99
- @deserializer_cache.materialize(result)
101
+ deserializer_cache.materialize(result)
100
102
  ensure
101
103
  result.clear if result
102
104
  end
@@ -119,7 +121,7 @@ module MiniSql
119
121
  if result.ntuples == 0
120
122
  # skip, this happens at the end when we get totals
121
123
  else
122
- materializer ||= @deserializer_cache.materializer(result)
124
+ materializer ||= deserializer_cache.materializer(result)
123
125
  result.type_map = type_map
124
126
  i = 0
125
127
  # technically we should only get 1 row here
@@ -170,7 +172,7 @@ module MiniSql
170
172
  def query_decorator(decorator, sql, *params)
171
173
  result = run(sql, params)
172
174
  result.type_map = type_map
173
- @deserializer_cache.materialize(result, decorator)
175
+ deserializer_cache.materialize(result, decorator)
174
176
  ensure
175
177
  result.clear if result
176
178
  end
@@ -6,10 +6,9 @@ module MiniSql
6
6
 
7
7
  attr_reader :unprepared
8
8
 
9
- def initialize(unprepared_connection, deserializer_cache)
9
+ def initialize(unprepared_connection)
10
10
  @unprepared = unprepared_connection
11
11
  @raw_connection = unprepared_connection.raw_connection
12
- @deserializer_cache = deserializer_cache
13
12
  @type_map = unprepared_connection.type_map
14
13
  @param_encoder = unprepared_connection.param_encoder
15
14
 
@@ -25,6 +24,10 @@ module MiniSql
25
24
  condition ? self : @unprepared
26
25
  end
27
26
 
27
+ def deserializer_cache
28
+ @unprepared.deserializer_cache
29
+ end
30
+
28
31
  private def run(sql, params)
29
32
  prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
30
33
  prepare_statement_key = @prepared_cache.prepare_statement(prepared_sql)
@@ -9,12 +9,14 @@ module MiniSql
9
9
  @raw_connection = raw_connection
10
10
  @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
11
11
  @deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
12
-
13
- @prepared = PreparedConnection.new(self, @deserializer_cache)
14
12
  end
15
13
 
16
14
  def prepared(condition = true)
17
- condition ? @prepared : self
15
+ if condition
16
+ @prepared ||= PreparedConnection.new(self)
17
+ else
18
+ self
19
+ end
18
20
  end
19
21
 
20
22
  def query_single(sql, *params)
@@ -6,10 +6,9 @@ module MiniSql
6
6
 
7
7
  attr_reader :unprepared
8
8
 
9
- def initialize(unprepared_connection, deserializer_cache)
9
+ def initialize(unprepared_connection)
10
10
  @unprepared = unprepared_connection
11
11
  @raw_connection = unprepared_connection.raw_connection
12
- @deserializer_cache = deserializer_cache
13
12
  @param_encoder = unprepared_connection.param_encoder
14
13
 
15
14
  @prepared_cache = PreparedCache.new(@raw_connection)
@@ -24,6 +23,10 @@ module MiniSql
24
23
  condition ? self : @unprepared
25
24
  end
26
25
 
26
+ def deserializer_cache
27
+ @unprepared.deserializer_cache
28
+ end
29
+
27
30
  private def run(sql, params)
28
31
  prepared_sql, binds, _bind_names = @param_binder.bind(sql, params)
29
32
  statement = @prepared_cache.prepare_statement(prepared_sql)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module MiniSql
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.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"
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
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'
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"
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.0
4
+ version: 1.2.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-22 00:00:00.000000000 Z
11
+ date: 2022-02-02 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
@@ -100,42 +100,42 @@ dependencies:
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
@@ -244,7 +244,7 @@ metadata:
244
244
  bug_tracker_uri: https://github.com/discourse/mini_sql/issues
245
245
  source_code_uri: https://github.com/discourse/mini_sql
246
246
  changelog_uri: https://github.com/discourse/mini_sql/blob/master/CHANGELOG
247
- post_install_message:
247
+ post_install_message:
248
248
  rdoc_options: []
249
249
  require_paths:
250
250
  - lib
@@ -259,8 +259,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
259
  - !ruby/object:Gem::Version
260
260
  version: '0'
261
261
  requirements: []
262
- rubygems_version: 3.2.2
263
- signing_key:
262
+ rubygems_version: 3.1.6
263
+ signing_key:
264
264
  specification_version: 4
265
265
  summary: A fast, safe, simple direct SQL executor
266
266
  test_files: []