quote-sql 0.0.2 → 0.0.3
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 +4 -4
- data/lib/quote_sql/quoter.rb +72 -11
- data/lib/quote_sql/test.rb +105 -36
- data/lib/quote_sql.rb +16 -5
- 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: 17336c46db1b966512f67b1c4dae714d612460ae61c1f24d75d0fa79153422df
|
4
|
+
data.tar.gz: 8b510daed8f21c7733e0b841f0c11f60c8634abde062e3a81dc844b8ed4cb682
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cc8dd6b2e36c5f5e976027d1f25442cf3cbee76b8bf5bf05b827ba2693d2aed952273491b1abd6404f53482b6da1c0e88e5de5cc9d25ac3af56b65f649f0aa7
|
7
|
+
data.tar.gz: 3e4a135613d5c22963030754e1bc21d8ac3d3aaf210abecdd2d8eeb47893f96c3243702c29d138321a122c28ed571670e32c0f703462cf1a1ca41e6b925f6da5
|
data/lib/quote_sql/quoter.rb
CHANGED
@@ -5,8 +5,16 @@ class QuoteSql
|
|
5
5
|
@key, @quotable = key, quotable
|
6
6
|
end
|
7
7
|
|
8
|
+
def quotes
|
9
|
+
@qsql.quotes
|
10
|
+
end
|
11
|
+
|
8
12
|
attr_reader :key, :quotable
|
9
13
|
|
14
|
+
def name
|
15
|
+
@key.sub(/_[^_]+$/, '')
|
16
|
+
end
|
17
|
+
|
10
18
|
def to_sql
|
11
19
|
return @quotable.call(self) if @quotable.is_a? Proc
|
12
20
|
case key.to_s
|
@@ -24,8 +32,10 @@ class QuoteSql
|
|
24
32
|
quotable.to_s
|
25
33
|
when /(?:^|(.*)_)(raw|sql)$/i
|
26
34
|
quotable.to_s
|
27
|
-
when
|
28
|
-
|
35
|
+
when /^(.+)_values$/i
|
36
|
+
data_values
|
37
|
+
when /values$/i
|
38
|
+
insert_values
|
29
39
|
else
|
30
40
|
quote
|
31
41
|
end
|
@@ -56,24 +66,75 @@ class QuoteSql
|
|
56
66
|
|
57
67
|
end
|
58
68
|
|
59
|
-
def
|
69
|
+
def data_values(item = @quotable)
|
70
|
+
item = Array(item).compact
|
71
|
+
column_names = @qsql.quotes[:"#{name}_columns"].dup
|
72
|
+
if column_names.is_a? Hash
|
73
|
+
types = column_names.values.map { "::#{_1.upcase}" if _1 }
|
74
|
+
column_names = column_names.keys
|
75
|
+
end
|
76
|
+
if item.all? { _1.is_a?(Hash) }
|
77
|
+
column_names ||= item.flat_map { _1.keys.sort }.uniq
|
78
|
+
item.map! { _1.fetch_values(*column_names) {} }
|
79
|
+
end
|
80
|
+
if item.all? { _1.is_a?(Array) }
|
81
|
+
length, overflow = item.map { _1.length }.uniq
|
82
|
+
raise ArgumentError, "all values need to have the same length" if overflow
|
83
|
+
column_names ||= (1..length).map{"column#{_1}"}
|
84
|
+
raise ArgumentError, "#{name}_columns and value lengths need to be the same" if column_names.length != length
|
85
|
+
values = item.map { value(_1) }
|
86
|
+
else
|
87
|
+
raise ArgumentError, "Either all type Hash or Array"
|
88
|
+
end
|
89
|
+
if types.present?
|
90
|
+
value = values[0][1..-2].split(/\s*,\s*/)
|
91
|
+
types.each_with_index { value[_2] << _1 || ""}
|
92
|
+
values[0] = "(" + value.join(",") + ")"
|
93
|
+
end
|
94
|
+
# values[0] { _1 << types[_1] || ""}
|
95
|
+
"(VALUES #{values.join(",")}) AS #{ident_name name} (#{ident_name column_names})"
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def insert_values(item = @quotable)
|
60
100
|
case item
|
61
101
|
when Arel::Nodes::SqlLiteral
|
62
102
|
item = Arel.sql("(#{item})") unless item[/^\s*\(/] and item[/\)\s*$/]
|
63
103
|
return item
|
64
104
|
when Array
|
105
|
+
item.compact!
|
106
|
+
column_names = (@qsql.quotes[:columns] || @qsql.quotes[:column_names]).dup
|
107
|
+
types = []
|
108
|
+
if column_names.is_a? Hash
|
109
|
+
types = column_names.values.map { "::#{_1.upcase}" if _1 }
|
110
|
+
column_names = column_names.keys
|
111
|
+
elsif column_names.is_a? Array
|
112
|
+
column_names = column_names.map do |column|
|
113
|
+
types << column.respond_to?(:sql_type) ? "::#{column.sql_type}" : nil
|
114
|
+
column.respond_to?(:name) ? column.name : column
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if item.all? { _1.is_a?(Hash) }
|
119
|
+
column_names ||= item.flat_map { _1.keys.sort }.uniq
|
120
|
+
item.map! { _1.fetch_values(*column_names) {} }
|
121
|
+
end
|
65
122
|
|
66
|
-
|
67
|
-
|
68
|
-
|
123
|
+
if item.all? { _1.is_a?(Array) }
|
124
|
+
length, overflow = item.map { _1.length }.uniq
|
125
|
+
raise ArgumentError, "all values need to have the same length" if overflow
|
126
|
+
raise ArgumentError, "#{name}_columns and value lengths need to be the same" if column_names and column_names.length != length
|
127
|
+
values = item.map { value(_1) }
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Either all type Hash or Array"
|
130
|
+
end
|
131
|
+
if column_names.present?
|
132
|
+
"(#{ident_name column_names}) VALUES #{values.join(",")}"
|
69
133
|
else
|
70
|
-
|
134
|
+
"VALUES #{values.join(",")}"
|
71
135
|
end
|
72
136
|
when Hash
|
73
137
|
value([item])
|
74
|
-
else
|
75
|
-
return item.to_sql if item.respond_to? :to_sql
|
76
|
-
"(" + _quote(item) + ")"
|
77
138
|
end
|
78
139
|
end
|
79
140
|
|
@@ -127,7 +188,7 @@ class QuoteSql
|
|
127
188
|
elsif item.class.respond_to?(:column_names)
|
128
189
|
item = item.class.column_names
|
129
190
|
elsif item.is_a?(Array)
|
130
|
-
if item
|
191
|
+
if item.all?{ _1.respond_to?(:name) }
|
131
192
|
item = item.map(&:name)
|
132
193
|
end
|
133
194
|
end
|
data/lib/quote_sql/test.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
module QuoteSql::Test
|
2
2
|
def self.all
|
3
|
+
@success = []
|
4
|
+
@fail = []
|
3
5
|
methods(false).grep(/^test_/).each do |name|
|
4
|
-
run(name)
|
5
|
-
puts
|
6
|
+
run(name, true)
|
6
7
|
end
|
7
|
-
|
8
|
+
@success.each { STDOUT.puts(*_1, nil) }
|
9
|
+
@fail.each { STDOUT.puts(*_1, nil) }
|
10
|
+
puts
|
8
11
|
end
|
9
12
|
|
10
|
-
def self.run(name)
|
13
|
+
def self.run(name, all)
|
11
14
|
name = name.to_s.sub(/^test_/, "")
|
12
15
|
@expected = nil
|
13
16
|
@test = send("test_#{name}")
|
14
|
-
|
15
|
-
|
17
|
+
|
18
|
+
if sql.gsub(/\s+/, "")&.downcase&.strip == expected&.gsub(/\s+/, "")&.downcase&.strip
|
19
|
+
rv = [name, @test.original, @test.quotes.inspect, "✅ #{expected}"]
|
20
|
+
@success << rv if @success
|
16
21
|
else
|
17
|
-
|
22
|
+
rv = [name, @test.inspect, sql, "❌ #{expected}"]
|
23
|
+
@fail << rv if @fail
|
18
24
|
end
|
25
|
+
STDOUT.puts rv unless @fail or @success
|
19
26
|
end
|
20
27
|
|
21
28
|
def self.expected(v = nil)
|
@@ -39,9 +46,10 @@ module QuoteSql::Test
|
|
39
46
|
"SELECT * FROM #{self.class.table_name}"
|
40
47
|
end
|
41
48
|
end
|
49
|
+
|
42
50
|
class << self
|
43
51
|
def test_columns_and_table_name_simple
|
44
|
-
expected
|
52
|
+
expected %(SELECT "a","b"."c" FROM "my_table")
|
45
53
|
QuoteSql.new("SELECT %columns FROM %table_name").quote(
|
46
54
|
columns: [:a, b: :c],
|
47
55
|
table_name: "my_table"
|
@@ -49,7 +57,7 @@ module QuoteSql::Test
|
|
49
57
|
end
|
50
58
|
|
51
59
|
def test_columns_and_table_name_complex
|
52
|
-
expected
|
60
|
+
expected %(SELECT "a","b"."c" FROM "table1","table2")
|
53
61
|
QuoteSql.new("SELECT %columns FROM %table_names").quote(
|
54
62
|
columns: [:a, b: :c],
|
55
63
|
table_names: ["table1", "table2"]
|
@@ -57,7 +65,7 @@ module QuoteSql::Test
|
|
57
65
|
end
|
58
66
|
|
59
67
|
def test_recursive_injects
|
60
|
-
expected
|
68
|
+
expected %(SELECT TRUE FROM "table1")
|
61
69
|
QuoteSql.new("SELECT %raw FROM %table_names").quote(
|
62
70
|
raw: "%recurse1_raw",
|
63
71
|
recurse1_raw: "%recurse2",
|
@@ -67,7 +75,9 @@ module QuoteSql::Test
|
|
67
75
|
end
|
68
76
|
|
69
77
|
def test_values
|
70
|
-
expected
|
78
|
+
expected <<~SQL
|
79
|
+
SELECT 'a text', 123, 'text' AS abc FROM "my_table"
|
80
|
+
SQL
|
71
81
|
QuoteSql.new("SELECT %text, %{number}, %aliased_with_hash FROM %table_name").quote(
|
72
82
|
text: "a text",
|
73
83
|
number: 123,
|
@@ -79,33 +89,92 @@ module QuoteSql::Test
|
|
79
89
|
end
|
80
90
|
|
81
91
|
def test_binds
|
82
|
-
expected
|
83
|
-
|
84
|
-
|
85
|
-
|
92
|
+
expected <<~SQL
|
93
|
+
SELECT $1, $2, $1 AS get_bind_1_again FROM "my_table"
|
94
|
+
SQL
|
95
|
+
QuoteSql.new("SELECT %bind, %bind__uuid, %bind1 AS get_bind_1_again FROM %table_name").quote(
|
96
|
+
table_name: "my_table"
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_from_values_array
|
101
|
+
expected <<~SQL
|
102
|
+
SELECT * FROM (VALUES ('a',1,TRUE,NULL)) AS "x" ("column1","column2","column3","column4")
|
103
|
+
SQL
|
104
|
+
"SELECT * FROM %x_values".quote_sql(x_values: [['a', 1, true, nil]])
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_from_values_hash_no_columns
|
108
|
+
expected <<~SQL
|
109
|
+
SELECT * FROM (VALUES ('a', 1, true, NULL), ('a', 1, true, NULL), (NULL, 1, NULL, 2)) AS "y" ("a", "b", "c", "d")
|
110
|
+
SQL
|
111
|
+
"SELECT * FROM %y_values".quote_sql(y_values: [
|
112
|
+
{ a: 'a', b: 1, c: true, d: nil },
|
113
|
+
{ d: nil, a: 'a', c: true, b: 1 },
|
114
|
+
{ d: 2, b: 1 }
|
115
|
+
])
|
86
116
|
end
|
87
117
|
|
88
|
-
def
|
89
|
-
expected
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
(2,NULL,'c','[1,2,3]','{"a":3}')
|
94
|
-
ON CONFLICT (responses_task_id_index_unique) DO NOTHING;
|
95
|
-
SQL
|
96
|
-
|
97
|
-
QuoteSql.new(<<-SQL).
|
98
|
-
INSERT INTO %table (%columns) VALUES %values
|
99
|
-
ON CONFLICT (responses_task_id_index_unique) DO NOTHING;
|
100
|
-
SQL
|
101
|
-
quote(
|
102
|
-
table: Response,
|
103
|
-
values: [
|
104
|
-
[nil, true, "A", [5, 5], { a: 1 }],
|
105
|
-
[1, false, "B", [], { a: 2 }],
|
106
|
-
[2, nil, "c", [1, 2, 3], { a: 3 }]
|
107
|
-
]
|
108
|
-
)
|
118
|
+
def test_from_values_hash_with_columns
|
119
|
+
expected <<~SQL
|
120
|
+
SELECT * FROM (VALUES (NULL, true, 1, 'a')) AS "x" ("d","c","b","a")
|
121
|
+
SQL
|
122
|
+
"SELECT * FROM %x_values".quote_sql(x_columns: %i[d c b a], x_values: [{ a: 'a', b: 1, c: true, d: nil }])
|
109
123
|
end
|
124
|
+
|
125
|
+
def test_from_values_hash_with_type_columns
|
126
|
+
expected <<~SQL
|
127
|
+
SELECT * FROM (VALUES ('a'::TEXT, 1::INTEGER, true::BOOLEAN, NULL::FLOAT), ('a', 1, true, NULL), (NULL, 1, NULL, 2)) AS "x" ("a", "b", "c", "d")
|
128
|
+
SQL
|
129
|
+
"SELECT * FROM %x_values".quote_sql(
|
130
|
+
x_columns: {
|
131
|
+
a: "text",
|
132
|
+
b: "integer",
|
133
|
+
c: "boolean",
|
134
|
+
d: "float"
|
135
|
+
},
|
136
|
+
x_values: [
|
137
|
+
{ a: 'a', b: 1, c: true, d: nil },
|
138
|
+
{ d: nil, a: 'a', c: true, b: 1 },
|
139
|
+
{ d: 2, b: 1 }
|
140
|
+
])
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_insert_values_array
|
144
|
+
expected <<~SQL
|
145
|
+
INSERT INTO x VALUES ('a', 1, true, NULL)
|
146
|
+
SQL
|
147
|
+
"INSERT INTO x %values".quote_sql(values: [['a', 1, true, nil]])
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_insert_values_hash
|
151
|
+
expected <<~SQL
|
152
|
+
INSERT INTO x ("a", "b", "c", "d") VALUES ('a', 1, true, NULL)
|
153
|
+
SQL
|
154
|
+
"INSERT INTO x %values".quote_sql(values: [{ a: 'a', b: 1, c: true, d: nil }])
|
155
|
+
end
|
156
|
+
|
157
|
+
# def test_q3
|
158
|
+
# expected Arel.sql(<<-SQL)
|
159
|
+
# INSERT INTO "responses" ("id","type","task_id","index","data","parts","value","created_at","updated_at")
|
160
|
+
# VALUES (NULL,TRUE,'A','[5,5]','{"a":1}'),
|
161
|
+
# (1,FALSE,'B','[]','{"a":2}'),
|
162
|
+
# (2,NULL,'c','[1,2,3]','{"a":3}')
|
163
|
+
# ON CONFLICT (responses_task_id_index_unique) DO NOTHING;
|
164
|
+
# SQL
|
165
|
+
#
|
166
|
+
# QuoteSql.new(<<-SQL).
|
167
|
+
# INSERT INTO %table (%columns) VALUES %values
|
168
|
+
# ON CONFLICT (responses_task_id_index_unique) DO NOTHING;
|
169
|
+
# SQL
|
170
|
+
# quote(
|
171
|
+
# table: Response,
|
172
|
+
# values: [
|
173
|
+
# [nil, true, "A", [5, 5], { a: 1 }],
|
174
|
+
# [1, false, "B", [], { a: 2 }],
|
175
|
+
# [2, nil, "c", [1, 2, 3], { a: 3 }]
|
176
|
+
# ]
|
177
|
+
# )
|
178
|
+
# end
|
110
179
|
end
|
111
180
|
end
|
data/lib/quote_sql.rb
CHANGED
@@ -151,18 +151,29 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
|
|
151
151
|
end
|
152
152
|
|
153
153
|
class Error < ::RuntimeError
|
154
|
-
def initialize(quote_sql)
|
154
|
+
def initialize(quote_sql, errors)
|
155
155
|
@object = quote_sql
|
156
|
+
@errors = errors
|
156
157
|
end
|
157
158
|
|
159
|
+
attr_reader :object, :errors
|
160
|
+
|
161
|
+
def sql
|
162
|
+
@object.original.inspect
|
163
|
+
end
|
164
|
+
|
165
|
+
# def inspect
|
166
|
+
# super + errors.flat_map { [_1.inspect, _1.backtrace] }
|
167
|
+
# end
|
168
|
+
|
158
169
|
def message
|
159
|
-
super + %Q@<QuoteSql #{
|
170
|
+
super + %Q@<QuoteSql #{sql} #{@object.errors.inspect}>@
|
160
171
|
end
|
161
172
|
end
|
162
173
|
|
163
174
|
def to_sql
|
164
175
|
mixin!
|
165
|
-
raise Error.new(self) if errors?
|
176
|
+
raise Error.new(self, errors) if errors?
|
166
177
|
return Arel.sql @sql if defined? Arel
|
167
178
|
@sql
|
168
179
|
end
|
@@ -224,8 +235,8 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
|
|
224
235
|
def errors
|
225
236
|
@quotes.to_h do |k, v|
|
226
237
|
r = @resolved[k]
|
227
|
-
next [nil, nil]
|
228
|
-
[k,
|
238
|
+
next [nil, nil] if r.nil? or not r.is_a?(Exception)
|
239
|
+
[k, {@quotes[k].inspect => v.inspect, exc: r, backtrace: r.backtrace}]
|
229
240
|
end.compact
|
230
241
|
end
|
231
242
|
|
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.3
|
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-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'QuoteSql helps you creating SQL queries and proper quoting especially
|
14
14
|
with advanced queries.
|