mini_sql 1.1.0 → 1.2.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.
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: []