mini_sql 1.3.0 → 1.5.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: 49e28eb6f0b011fa819621155a5f4507140035c5c4386d8dbcd297ea81981ad8
4
- data.tar.gz: 0dd3f883623ab47c530ebdce212f32cd02a2eb76dd9b51a27e165425b1dad974
3
+ metadata.gz: 491de0ce7925ad9ec428409af4a78e5ce3f91b6f886bfcca7c17544a4b44e23a
4
+ data.tar.gz: 2269da4aa96ec6d58c2141b82cae6d3764664e312ada254fe7f45476e6cb5b55
5
5
  SHA512:
6
- metadata.gz: e52ffd9133bd63060e6f5f7373958196ba56e5e1ffa38b910fe1119154f06bb076634aa18b5cad3c13045fcbf5cc6990f7966db258aea455dda62a67f2d608ee
7
- data.tar.gz: a3d8370bd15bf01f150c58521da264bddfd4e766d370fa776d052d22a0b484513e2b8fa8b1588c352b4c87c023876b65cfa117655fc0c5390b5da9ea64f5751f
6
+ metadata.gz: dddf4370cdff6041fc2a0ceb338aa387ddcca76b65e135838cd0c3a1bfcf0bd0fcae37ea7504052b89b505b8c5478fb6d11e260e2349e25131b01853d42eae2d
7
+ data.tar.gz: 8d2f7087f39d05a4e8ccf72e675a07a38d9f371bdcfef33d96589a085f13abef53feb6986668fb12c75886ff9db301c0b7753b36e72177ef33944f08ce347734
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ 2023-08-16 - 1.5.0
2
+
3
+ - FEATURE: add to_sql for easy conversion of builder to sql
4
+ - FEATURE: improve active record compat
5
+ - FEATURE: change builder to use gsub vs sub, which allows repeat clauses
6
+
7
+ 2022-03-07 - 1.4.0
8
+
9
+ - PERF: Optimize multiple use param in prepared postgres sql
10
+
1
11
  2022-02-02 - 1.3.0
2
12
 
3
13
  - FEATURE: Add ActiveRecordPostgres connection
data/README.md CHANGED
@@ -77,23 +77,34 @@ builder.query.each do |t|
77
77
  end
78
78
  ```
79
79
 
80
+ The same builder's statement may occur multiple times.
81
+
82
+ ```ruby
83
+ builder = conn.build('(/*select*/ from books) union (/*select*/ from movies)').select('title').query
84
+
85
+ # => (SELECT title from books) union (SELECT title from movies)
86
+ ```
87
+
80
88
  The builder predefined next _SQL Literals_
81
89
 
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*/`|
90
+ | Method | SQL Literal |
91
+ |-------------|--------------|
92
+ | `select` | `/*select*/` |
93
+ | `count` | `/*select*/` |
94
+ | `where` | `/*where*/` |
95
+ | `where_or` | `/*where*/` |
96
+ | `where2` | `/*where2*/` |
97
+ | `where2_or` | `/*where2*/` |
98
+ | `join` | `/*join*/` |
99
+ | `left_join` | `/*left_join*/` |
100
+ | `group_by` | `/*group_by*/` |
101
+ | `order_by` | `/*order_by*/` |
102
+ | `limit` | `/*limit*/` |
103
+ | `offset` | `/*offset*/` |
104
+ | `set` | `/*set*/` |
94
105
 
95
106
  ### Custom SQL Literals
96
- Use `sql_literal` for injecting custom sql into Builder
107
+ Use `sql_literal` to inject SQL into Builder from `String`, `Builder`, `ActiveRecord::Relation`, or any object that implements `to_sql` method.
97
108
 
98
109
  ```ruby
99
110
  user_builder = conn
@@ -101,9 +112,9 @@ user_builder = conn
101
112
  .where('type = ?', input_type)
102
113
  .group_by("date_trunc('day', created_at)")
103
114
 
104
- guest_builder = conn
105
- .build("select date_trunc('day', created_at) day, count(*) from guest_topics /*where*/")
106
- .where('state = ?', input_state)
115
+ guest_relation = GuestTopic
116
+ .select("date_trunc('day', created_at) day, count(*)")
117
+ .where(state: input_state)
107
118
  .group_by("date_trunc('day', created_at)")
108
119
 
109
120
  conn
@@ -113,8 +124,8 @@ conn
113
124
  from u
114
125
  /*custom_join*/
115
126
  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
127
+ .sql_literal(user: user_builder, guest: guest_relation) # Builder and ActiveRecord::Relation
128
+ .sql_literal(custom_join: "#{input_cond ? 'FULL' : 'LEFT'} JOIN g on g.day = u.day") # or String
118
129
  .query
119
130
  ```
120
131
 
@@ -11,8 +11,13 @@ class MiniSql::Builder
11
11
  @is_prepared = false
12
12
  end
13
13
 
14
+ def initialize_copy(_original_builder)
15
+ @args = @args.transform_values { |v| v.dup }
16
+ @sections = @sections.transform_values { |v| v.dup }
17
+ end
18
+
14
19
  literals1 =
15
- [:set, :where2, :where, :order_by, :left_join, :join, :select, :group_by].each do |k|
20
+ [:set, :where2, :where2_or, :where, :where_or, :order_by, :left_join, :join, :select, :group_by].each do |k|
16
21
  define_method k do |sql_part, *args|
17
22
  if Hash === args[0]
18
23
  @args.merge!(args[0])
@@ -48,7 +53,7 @@ class MiniSql::Builder
48
53
  if PREDEFINED_SQL_LITERALS.include?(name)
49
54
  raise "/*#{name}*/ is predefined, use method `.#{name}` instead `sql_literal`"
50
55
  end
51
- @sections[name] = part_sql.is_a?(::MiniSql::Builder) ? part_sql.to_sql : part_sql
56
+ @sections[name] = part_sql.respond_to?(:to_sql) ? part_sql.to_sql : part_sql
52
57
  end
53
58
  self
54
59
  end
@@ -75,6 +80,10 @@ class MiniSql::Builder
75
80
  @connection.param_encoder.encode(parametrized_sql, union_parameters(hash_args))
76
81
  end
77
82
 
83
+ def count(field = '*')
84
+ dup.select("count(#{field})").query_single.first
85
+ end
86
+
78
87
  private def connection_switcher
79
88
  if @is_prepared
80
89
  @connection.prepared
@@ -83,35 +92,43 @@ class MiniSql::Builder
83
92
  end
84
93
  end
85
94
 
95
+ WHERE_SECTIONS = [%i[where where_or], %i[where2 where2_or]]
86
96
  private def parametrized_sql
87
97
  sql = @sql.dup
88
98
 
89
- @sections.each do |k, v|
90
- joined = nil
91
- case k
92
- when :select
93
- joined = (+"SELECT ") << v.join(" , ")
94
- when :where, :where2
95
- joined = (+"WHERE ") << v.map { |c| (+"(") << c << ")" }.join(" AND ")
96
- when :join
97
- joined = v.map { |item| (+"JOIN ") << item }.join("\n")
98
- when :left_join
99
- joined = v.map { |item| (+"LEFT JOIN ") << item }.join("\n")
100
- when :limit
101
- joined = (+"LIMIT :mq_auto_limit")
102
- when :offset
103
- joined = (+"OFFSET :mq_auto_offset")
104
- when :order_by
105
- joined = (+"ORDER BY ") << v.join(" , ")
106
- when :group_by
107
- joined = (+"GROUP BY ") << v.join(" , ")
108
- when :set
109
- joined = (+"SET ") << v.join(" , ")
110
- else # for sql_literal
111
- joined = v
99
+ WHERE_SECTIONS.each do |section_and, section_or|
100
+ if (or_values = @sections.delete(section_or))
101
+ @sections[section_and] ||= []
102
+ @sections[section_and] << or_values.map { |c| "(#{c})" }.join(" OR ")
112
103
  end
104
+ end
105
+
106
+ @sections.each do |k, v|
107
+ joined =
108
+ case k
109
+ when :select
110
+ "SELECT #{v.join(" , ")}"
111
+ when :where, :where2
112
+ "WHERE #{v.map { |c| "(#{c})" }.join(" AND ")}"
113
+ when :join
114
+ v.map { |item| "JOIN #{item}" }.join("\n")
115
+ when :left_join
116
+ v.map { |item| "LEFT JOIN #{item}" }.join("\n")
117
+ when :limit
118
+ "LIMIT :mq_auto_limit"
119
+ when :offset
120
+ "OFFSET :mq_auto_offset"
121
+ when :order_by
122
+ "ORDER BY #{v.join(" , ")}"
123
+ when :group_by
124
+ "GROUP BY #{v.join(" , ")}"
125
+ when :set
126
+ "SET #{v.join(" , ")}"
127
+ else # for sql_literal
128
+ v
129
+ end
113
130
 
114
- unless sql.sub!("/*#{k}*/", joined)
131
+ unless sql.gsub!("/*#{k}*/", joined)
115
132
  raise "The section for the /*#{k}*/ clause was not found!"
116
133
  end
117
134
  end
@@ -57,6 +57,14 @@ module MiniSql
57
57
  Builder.new(self, sql)
58
58
  end
59
59
 
60
+ def to_sql(sql, *params)
61
+ if params.empty?
62
+ sql
63
+ else
64
+ param_encoder.encode(sql, *params)
65
+ end
66
+ end
67
+
60
68
  def escape_string(str)
61
69
  raise NotImplementedError, "must be implemented by child connection"
62
70
  end
@@ -51,18 +51,11 @@ module MiniSql
51
51
  raw_connection.escape(str)
52
52
  end
53
53
 
54
- def build(sql)
55
- Builder.new(self, sql)
56
- end
57
-
58
54
  private
59
55
 
60
56
  def run(sql, as, params)
61
- if params && params.length > 0
62
- sql = param_encoder.encode(sql, *params)
63
- end
64
57
  raw_connection.query(
65
- sql,
58
+ to_sql(sql, *params),
66
59
  as: as,
67
60
  database_timezone: :utc,
68
61
  application_timezone: :utc,
@@ -192,10 +192,6 @@ module MiniSql
192
192
  result.clear if result
193
193
  end
194
194
 
195
- def build(sql)
196
- Builder.new(self, sql)
197
- end
198
-
199
195
  def escape_string(str)
200
196
  raw_connection.escape_string(str)
201
197
  end
@@ -203,10 +199,7 @@ module MiniSql
203
199
  private
204
200
 
205
201
  def run(sql, params)
206
- if params && params.length > 0
207
- sql = param_encoder.encode(sql, *params)
208
- end
209
- raw_connection.async_exec(sql)
202
+ raw_connection.async_exec(to_sql(sql, *params))
210
203
  end
211
204
 
212
205
  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
@@ -78,10 +78,6 @@ module MiniSql
78
78
  run(sql, params).to_a
79
79
  end
80
80
 
81
- def build(sql)
82
- Builder.new(self, sql)
83
- end
84
-
85
81
  def escape_string(str)
86
82
  raw_connection.escape_string(str)
87
83
  end
@@ -89,12 +85,9 @@ module MiniSql
89
85
  private
90
86
 
91
87
  def run(sql, params)
92
- if params && params.length > 0
93
- sql = param_encoder.encode(sql, *params)
94
- end
95
88
  conn = raw_connection
96
89
  conn.typemap = self.class.typemap
97
- conn.execute(sql)
90
+ conn.execute(to_sql(sql, *params))
98
91
  ensure
99
92
  # Force unsetting of typemap since we don't want mixed AR usage to continue to use these extra converters.
100
93
  conn.typemap = nil
@@ -21,12 +21,12 @@ module MiniSql
21
21
 
22
22
  def query_single(sql, *params)
23
23
  # a bit lazy can be optimized
24
- run(sql, *params).flatten!
24
+ run(sql, params).flatten!
25
25
  end
26
26
 
27
27
  def query_hash(sql, *params)
28
28
  r = []
29
- run(sql, *params) do |set|
29
+ run(sql, params) do |set|
30
30
  set.each_hash do |h|
31
31
  r << h
32
32
  end
@@ -35,14 +35,14 @@ module MiniSql
35
35
  end
36
36
 
37
37
  def query_array(sql, *params)
38
- run(sql, *params)
38
+ run(sql, params)
39
39
  end
40
40
 
41
41
  def exec(sql, *params)
42
42
 
43
43
  start = raw_connection.total_changes
44
44
 
45
- r = run(sql, *params)
45
+ r = run(sql, params)
46
46
  # this is not safe for multithreading, also for DELETE from TABLE will return
47
47
  # incorrect data
48
48
  if r.length > 0
@@ -53,13 +53,13 @@ module MiniSql
53
53
  end
54
54
 
55
55
  def query(sql, *params)
56
- run(sql, *params) do |set|
56
+ run(sql, params) do |set|
57
57
  deserializer_cache.materialize(set)
58
58
  end
59
59
  end
60
60
 
61
61
  def query_decorator(decorator, sql, *params)
62
- run(sql, *params) do |set|
62
+ run(sql, params) do |set|
63
63
  deserializer_cache.materialize(set, decorator)
64
64
  end
65
65
  end
@@ -70,10 +70,8 @@ module MiniSql
70
70
 
71
71
  private
72
72
 
73
- def run(sql, *params)
74
- if params && params.length > 0
75
- sql = param_encoder.encode(sql, *params)
76
- end
73
+ def run(sql, params)
74
+ sql = to_sql(sql, *params)
77
75
  if block_given?
78
76
  stmt = SQLite3::Statement.new(raw_connection, sql)
79
77
  result = yield stmt.execute
@@ -28,7 +28,7 @@ module MiniSql
28
28
  end
29
29
 
30
30
  private def run(sql, params)
31
- prepared_sql, binds, _bind_names = @param_binder.bind(sql, params)
31
+ prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
32
32
  statement = @prepared_cache.prepare_statement(prepared_sql)
33
33
  statement.bind_params(binds)
34
34
  if block_given?
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module MiniSql
3
- VERSION = "1.3.0"
3
+ VERSION = "1.5.0"
4
4
  end
data/mini_sql.gemspec CHANGED
@@ -47,7 +47,7 @@ Gem::Specification.new do |spec|
47
47
  else
48
48
  spec.add_development_dependency "pg", "> 1"
49
49
  spec.add_development_dependency "mysql2"
50
- spec.add_development_dependency "sqlite3", "~> 1.3"
50
+ spec.add_development_dependency "sqlite3", "~> 1.4.4"
51
51
  spec.add_development_dependency "activerecord", "~> 7.0.0"
52
52
  end
53
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.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-02 00:00:00.000000000 Z
11
+ date: 2023-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -170,14 +170,14 @@ dependencies:
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '1.3'
173
+ version: 1.4.4
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '1.3'
180
+ version: 1.4.4
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: activerecord
183
183
  requirement: !ruby/object:Gem::Requirement