quote-sql 0.0.4 โ†’ 0.0.5

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: 70f787b04560e3b297b4a392eb72c0e85be57e349a0533366056fe485f339401
4
- data.tar.gz: ad8a2e347c97e12b78a264e67d0e12649e838127a0079d8ff96bf5ac51764b40
3
+ metadata.gz: cb47e85b4c7c4e1f945748295390c9e1bd49b7e0ba5291e0f7310058f6f91ddc
4
+ data.tar.gz: 0f5bde1afc31eb573bb3e24727b530bf5909048284114d45c56c1b24a63e97aa
5
5
  SHA512:
6
- metadata.gz: 7d2a6819ad0d7fc752a70a04efb48c7583759a1b6cd07eb93c0bacd5deec9845b4abf578ae902c94aaf68ba4cf41720b1f05aea8dc6950b468c8cc8f42c34c42
7
- data.tar.gz: e84a311154ef05f71b8af81c17cac1b4923f067fb48556f501aa075ab8fca1c112d494c8b0b58f1c8350d7d7467c10ba99f74591755beb206415dbe78756c7ff
6
+ metadata.gz: 6596b9bb50ee373030e401dcb9d0067ae31857b6467fdd7b0341e21b3dcbe8fb0e78714ec24f56615618bbf965849307474a4030978e6c98d2d728166e235151
7
+ data.tar.gz: f38366de3e2227835fd11f512a9c9d2a99d130d896a17f6b0a58788dd8ed7ebd13f46682992b762e2d50f09716c3bd5654d525dc4b03f350d3a52cc6b19b322e
data/README.md CHANGED
@@ -9,13 +9,15 @@ I created this library while coding for different projects, and had lots of Here
9
9
 
10
10
  My strategy is to segment SQL Queries in readable junks, which can be individually tested and then combine their sql to the final query.
11
11
 
12
- QuoteSql is used in production, but is still bleeding edge - and there is not a fully sync between doc and code.
13
-
14
12
  If you think QuoteSql is interesting, let's chat!
15
13
  Also if you have problems using it, just drop me a note.
16
14
 
17
15
  Best Martin
18
16
 
17
+ ## Caveats & Notes
18
+ - QuoteSql is used in production, but is still bleeding edge - and there is not a fully sync between doc and code.
19
+ - Just for my examples and in the docs, I'm using for Yajl for JSON parsing, and changed in my environments the standard parse output to *symbolized keys*.
20
+
19
21
  ## Examples
20
22
  ### Simple quoting
21
23
  `QuoteSql.new("SELECT %field").quote(field: "abc").to_sql`
@@ -119,6 +121,12 @@ with optional array dimension
119
121
  - +String+ value will become the expression, the key the AS {result: "SUM(*)"} => SUM(*) AS result
120
122
  - +Proc+ are executed with the +QuoteSQL::Quoter+ object as parameter and added as raw SQL
121
123
 
124
+ ## Executing
125
+ ### Getting the results
126
+ ### Binds
127
+ `v = {a: 1, b: "foo", c: true};QuoteSQL(%q{Select * From %x_json}, x_json: 1, x_casts: {a: "int", b: "text", c: "boolean"}).result(v.to_json)`
128
+ => Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean) => [{a: 1, b: "foo", c: true}]
129
+
122
130
  ## Shortcuts and functions
123
131
  - `QuoteSQL("select %abc", abc: 1)` == `QuoteSql.new("select %abc").quote(abc: 1)`
124
132
  - when you have in your initializer `String.include QuoteSql::Extension` you can do e.g. `"select %abc".quote_sql(abc: 1)`
@@ -37,9 +37,13 @@ class QuoteSql
37
37
  def ident_columns(name = nil)
38
38
  item = columns(name || self.name)
39
39
  unless item
40
- table = self.table(name || self.name)
41
- raise ArgumntError, "No columns or table given" unless table&.respond_to? :column_names
42
- item = table.column_names
40
+ unless item = casts(name || self.name)&.keys
41
+ if (table = self.table(name || self.name))&.respond_to? :column_names
42
+ item = table.column_names
43
+ else
44
+ raise ArgumntError, "No columns, casts or table given for #{name}" unless table&.respond_to? :column_names
45
+ end
46
+ end
43
47
  end
44
48
  if item.is_a?(Array)
45
49
  if item.all? { _1.respond_to?(:name) }
@@ -143,11 +147,15 @@ class QuoteSql
143
147
  casts = self.casts(name)
144
148
  columns = self.columns(name) || casts&.keys
145
149
  column_cast = columns&.map { "#{QuoteSql.quote_column_name(_1)} #{casts&.dig(_1) || "TEXT"}" }
146
- item = [item].flatten.compact.as_json.map{_1.slice(*columns.map(&:to_s))}
147
- Raw.sql "json_to_recordset('#{item.to_json.gsub(/'/,"''")}') AS #{QuoteSql.quote_column_name name}(#{column_cast.join(',')})"
150
+ if item.is_a? Integer
151
+ rv = "$#{item}"
152
+ else
153
+ item = [item].flatten.compact.as_json.map { _1.slice(*columns.map(&:to_s)) }
154
+ rv = "'#{item.to_json.gsub(/'/, "''")}'"
155
+ end
156
+ Raw.sql "json_to_recordset(#{rv}) AS #{QuoteSql.quote_column_name name}(#{column_cast.join(',')})"
148
157
  end
149
158
 
150
-
151
159
  def data_values(item = @quotable)
152
160
  item = Array(item).compact
153
161
  column_names = columns(name)
@@ -1,59 +1,5 @@
1
1
  class QuoteSql::Test
2
-
3
- def all
4
- @success = []
5
- @fail = []
6
- private_methods(false).grep(/^test_/).each { run(_1, true) }
7
- @success.each { STDOUT.puts(*_1, nil) }
8
- @fail.each { STDOUT.puts(*_1, nil) }
9
- puts
10
- end
11
-
12
- def run(name, all = false)
13
- name = name.to_s.sub(/^test_/, "")
14
- rv = ["๐Ÿงช #{name}"]
15
- @expected = nil
16
- @test = send("test_#{name}")
17
- if sql.gsub(/\s+/, "")&.downcase&.strip == expected&.gsub(/\s+/, "")&.downcase&.strip
18
- tables = @test.tables.to_h { [[_1, "table"].compact.join("_"), _2] }
19
- columns = @test.instance_variable_get(:@columns).to_h { [[_1, "columns"].compact.join("_"), _2] }
20
- rv += [@test.original, { **tables, **columns, **@test.quotes }.inspect, "๐ŸŽฏ #{expected}", "โœ… #{sql}"]
21
- @success << rv if @success
22
- else
23
- rv += [@test.inspect, "๐ŸŽฏ #{expected}", "โŒ #{sql}"]
24
- @fail << rv if @fail
25
- end
26
- rescue => exc
27
- rv += [@test.inspect, "๐ŸŽฏ #{expected}", "โŒ #{sql}", exc.message]
28
- @fail << rv if @fail
29
- ensure
30
- STDOUT.puts(*rv) unless @fail or @success
31
- end
32
-
33
- def expected(v = nil)
34
- @expected ||= v
35
- end
36
-
37
- def sql
38
- @test.to_sql
39
- end
40
-
41
- class PseudoActiveRecord
42
- def self.table_name
43
- "pseudo_active_records"
44
- end
45
-
46
- def self.column_names
47
- %w(id column1 column2)
48
- end
49
-
50
- def to_qsl
51
- "SELECT * FROM #{self.class.table_name}"
52
- end
53
- end
54
-
55
2
  private
56
-
57
3
  def test_columns
58
4
  expected <<~SQL
59
5
  SELECT x, "a", "b", "c", "d"
@@ -189,6 +135,20 @@ class QuoteSql::Test
189
135
  "INSERT INTO users (name, color) SELECT * from %x_json".quote_sql(x_casts: {name: "text", color: "text"}, x_json:)
190
136
  end
191
137
 
138
+ def test_from_json_bind
139
+ expected <<~SQL
140
+ Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
141
+ SQL
142
+ QuoteSQL("Select * From %x_json", x_json: 1, x_casts: {a: "int", b: "text", c: "boolean"})
143
+ end
144
+
145
+ def test_insert_json_bind
146
+ expected <<~SQL
147
+ INSERT INTO table ("a","b","c") Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
148
+ SQL
149
+ QuoteSQL("INSERT INTO table (%x_columns) Select * From %x_json", x_json: 1, x_casts: {a: "int", b: "text", c: "boolean"})
150
+ end
151
+
192
152
  # def test_q3
193
153
  # expected Arel.sql(<<-SQL)
194
154
  # INSERT INTO "responses" ("id","type","task_id","index","data","parts","value","created_at","updated_at")
@@ -212,4 +172,60 @@ class QuoteSql::Test
212
172
  # )
213
173
  # end
214
174
 
175
+
176
+ public
177
+
178
+ def all
179
+ @success = []
180
+ @fail = []
181
+ private_methods(false).grep(/^test_/).each { run(_1, true) }
182
+ @success.each { STDOUT.puts(*_1, nil) }
183
+ @fail.each { STDOUT.puts(*_1, nil) }
184
+ puts
185
+ end
186
+
187
+ def run(name, all = false)
188
+ name = name.to_s.sub(/^test_/, "")
189
+ rv = ["๐Ÿงช #{name}"]
190
+ @expected = nil
191
+ @test = send("test_#{name}")
192
+ if sql.gsub(/\s+/, "")&.downcase&.strip == expected&.gsub(/\s+/, "")&.downcase&.strip
193
+ tables = @test.tables.to_h { [[_1, "table"].compact.join("_"), _2] }
194
+ columns = @test.instance_variable_get(:@columns).to_h { [[_1, "columns"].compact.join("_"), _2] }
195
+ rv += [
196
+ "QuoteSql.new(\"#{@test.original}\").quote(#{{**tables, **columns, **@test.quotes }.inspect}).to_sql", "๐ŸŽฏ #{expected}", "โœ… #{sql}"]
197
+ @success << rv if @success
198
+ else
199
+ rv += [@test.inspect, "๐ŸŽฏ #{expected}", "โŒ #{sql}"]
200
+ @fail << rv if @fail
201
+ end
202
+ rescue => exc
203
+ rv += [@test.inspect, "๐ŸŽฏ #{expected}", "โŒ #{sql}", exc.message]
204
+ @fail << rv if @fail
205
+ ensure
206
+ STDOUT.puts(*rv) unless @fail or @success
207
+ end
208
+
209
+ def expected(v = nil)
210
+ @expected ||= v
211
+ end
212
+
213
+ def sql
214
+ @test.to_sql
215
+ end
216
+
217
+ class PseudoActiveRecord
218
+ def self.table_name
219
+ "pseudo_active_records"
220
+ end
221
+
222
+ def self.column_names
223
+ %w(id column1 column2)
224
+ end
225
+
226
+ def to_qsl
227
+ "SELECT * FROM #{self.class.table_name}"
228
+ end
229
+ end
230
+
215
231
  end
@@ -1,3 +1,3 @@
1
1
  class QuoteSql
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/quote_sql.rb CHANGED
@@ -82,14 +82,14 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
82
82
  @sql
83
83
  end
84
84
 
85
- def result(binds = [], prepare: false, async: false)
85
+ def result(*binds, prepare: false, async: false)
86
86
  sql = to_sql
87
- if binds.present? and sql.scan(/(?<=\$)\d+/).map(&:to_i).max + 1 != binds.length
87
+ if binds.present? and sql.scan(/(?<=\$)\d+/).map(&:to_i).max != binds.length
88
88
  raise ArgumentError, "Wrong number of binds"
89
89
  end
90
90
  _exec(sql, binds, prepare: false, async: false)
91
91
  rescue => exc
92
- STDERR.puts exc.sql
92
+ STDERR.puts exc.inspect, self.inspect
93
93
  raise exc
94
94
  end
95
95
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quote-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Kufner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-26 00:00:00.000000000 Z
11
+ date: 2024-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: niceql