quote-sql 0.0.4 โ 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -2
- data/lib/quote_sql/quoter.rb +14 -6
- data/lib/quote_sql/test.rb +70 -54
- data/lib/quote_sql/version.rb +1 -1
- data/lib/quote_sql.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb47e85b4c7c4e1f945748295390c9e1bd49b7e0ba5291e0f7310058f6f91ddc
|
4
|
+
data.tar.gz: 0f5bde1afc31eb573bb3e24727b530bf5909048284114d45c56c1b24a63e97aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)`
|
data/lib/quote_sql/quoter.rb
CHANGED
@@ -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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
147
|
-
|
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)
|
data/lib/quote_sql/test.rb
CHANGED
@@ -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
|
data/lib/quote_sql/version.rb
CHANGED
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
|
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
|
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.
|
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
|
+
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-
|
11
|
+
date: 2024-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: niceql
|