json2sql 1.0.0 → 1.0.2
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/json2sql/delete_model.rb +13 -2
- data/lib/json2sql/delete_runner.rb +18 -2
- data/lib/json2sql/insert_model.rb +30 -4
- data/lib/json2sql/insert_runner.rb +16 -1
- data/lib/json2sql/sanitizer.rb +7 -0
- data/lib/json2sql/select_model.rb +106 -3
- data/lib/json2sql/select_runner.rb +18 -3
- data/lib/json2sql/update_model.rb +26 -4
- data/lib/json2sql/update_runner.rb +18 -2
- data/lib/json2sql/version.rb +1 -1
- data/lib/json2sql/where_model.rb +168 -46
- data/lib/json2sql/where_relation.rb +38 -14
- data/lib/json2sql.rb +10 -8
- 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: 5aced53d0fa190feb8e8d3fae69da28a27d7bcf4a87ff5eb220a3603b63780ec
|
|
4
|
+
data.tar.gz: 9b6b8e0c12c009823530de7af8394a8303a3edaed31a3f25bd3a21027821277f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48dfc9b428a3005570e77b2a4085d4b963eeae8740b1887341a8aa1c2eaf9194e33ececdfa603468d301121385c1b76fe93ffcdc66d0440bd112cb89ff72056d
|
|
7
|
+
data.tar.gz: e8ec74d97dc1ebe87127e271db0eb50a27909ad164105217740d21f291c2041d7f8351cc915276f3806920b0666a4d055cea49868b1172a7d2b624748c2efe0f
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds a DELETE FROM statement for a single table.
|
|
3
4
|
#
|
|
4
5
|
# Input Hash:
|
|
5
6
|
# "and" => { ... } – WHERE conditions (required to avoid deleting all rows)
|
|
6
7
|
# "or" => { ... } – WHERE conditions (OR)
|
|
8
|
+
|
|
7
9
|
class DeleteModel
|
|
10
|
+
|
|
8
11
|
def initialize(sql, table, relation)
|
|
9
|
-
|
|
10
|
-
@
|
|
12
|
+
|
|
13
|
+
@sql = sql
|
|
14
|
+
|
|
15
|
+
@table = table.to_s
|
|
16
|
+
|
|
11
17
|
@relation = relation
|
|
12
18
|
end
|
|
13
19
|
|
|
14
20
|
def build(params)
|
|
21
|
+
|
|
15
22
|
@sql << "DELETE FROM "
|
|
23
|
+
|
|
16
24
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
25
|
+
|
|
17
26
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
18
27
|
end
|
|
28
|
+
|
|
19
29
|
end
|
|
30
|
+
|
|
20
31
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds one or more DELETE statements from a Hash of table → params.
|
|
3
4
|
#
|
|
4
5
|
# Usage (single deletion):
|
|
@@ -13,24 +14,37 @@ module Json2sql
|
|
|
13
14
|
# { "and" => { "user_id" => 2 } }
|
|
14
15
|
# ]
|
|
15
16
|
# )
|
|
17
|
+
|
|
16
18
|
class DeleteRunner
|
|
19
|
+
|
|
17
20
|
def self.build(input)
|
|
18
|
-
|
|
19
|
-
sql
|
|
21
|
+
|
|
22
|
+
sql = +""
|
|
23
|
+
|
|
24
|
+
input = Json2sql.normalize(input)
|
|
25
|
+
|
|
20
26
|
relation = WhereRelation.none("")
|
|
21
27
|
|
|
22
28
|
input.each do |table, value|
|
|
29
|
+
|
|
23
30
|
tbl = table.to_s
|
|
24
31
|
|
|
25
32
|
case value
|
|
33
|
+
|
|
26
34
|
when Hash
|
|
35
|
+
|
|
27
36
|
DeleteModel.new(sql, tbl, relation).build(value)
|
|
37
|
+
|
|
28
38
|
sql << ";\n"
|
|
39
|
+
|
|
29
40
|
when Array
|
|
41
|
+
|
|
30
42
|
value.each do |item|
|
|
43
|
+
|
|
31
44
|
next unless item.is_a?(Hash)
|
|
32
45
|
|
|
33
46
|
DeleteModel.new(sql, tbl, relation).build(item)
|
|
47
|
+
|
|
34
48
|
sql << ";\n"
|
|
35
49
|
end
|
|
36
50
|
end
|
|
@@ -38,5 +52,7 @@ module Json2sql
|
|
|
38
52
|
|
|
39
53
|
sql
|
|
40
54
|
end
|
|
55
|
+
|
|
41
56
|
end
|
|
57
|
+
|
|
42
58
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds an INSERT INTO statement for a single table.
|
|
3
4
|
#
|
|
4
5
|
# Input Hash:
|
|
@@ -7,50 +8,75 @@ module Json2sql
|
|
|
7
8
|
# Values:
|
|
8
9
|
# Integer / Float → inserted as raw numbers
|
|
9
10
|
# String → wrapped in single quotes with SQL escaping
|
|
11
|
+
|
|
10
12
|
class InsertModel
|
|
13
|
+
|
|
11
14
|
def initialize(sql, table)
|
|
12
|
-
|
|
15
|
+
|
|
16
|
+
@sql = sql
|
|
17
|
+
|
|
13
18
|
@table = table.to_s
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def build(params)
|
|
22
|
+
|
|
17
23
|
@sql << "INSERT INTO "
|
|
24
|
+
|
|
18
25
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
26
|
+
|
|
19
27
|
@sql << " ("
|
|
28
|
+
|
|
20
29
|
build_columns(params)
|
|
30
|
+
|
|
21
31
|
@sql << ") VALUES ("
|
|
32
|
+
|
|
22
33
|
build_values(params)
|
|
34
|
+
|
|
23
35
|
@sql << ")"
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
private
|
|
27
39
|
|
|
28
40
|
def build_columns(params)
|
|
29
|
-
|
|
41
|
+
|
|
42
|
+
columns = params["columns"]
|
|
43
|
+
|
|
30
44
|
return unless columns.is_a?(Hash)
|
|
31
45
|
|
|
32
46
|
separator = false
|
|
47
|
+
|
|
33
48
|
columns.each_key do |key|
|
|
49
|
+
|
|
34
50
|
@sql << ", " if separator
|
|
51
|
+
|
|
35
52
|
separator = true
|
|
53
|
+
|
|
36
54
|
@sql << Sanitizer.keyword_wrap(key.to_s)
|
|
37
55
|
end
|
|
38
56
|
end
|
|
39
57
|
|
|
40
58
|
def build_values(params)
|
|
59
|
+
|
|
41
60
|
columns = params["columns"]
|
|
61
|
+
|
|
42
62
|
return unless columns.is_a?(Hash)
|
|
43
63
|
|
|
44
64
|
separator = false
|
|
65
|
+
|
|
45
66
|
columns.each_value do |value|
|
|
67
|
+
|
|
46
68
|
@sql << ", " if separator
|
|
69
|
+
|
|
47
70
|
separator = true
|
|
48
71
|
|
|
49
72
|
case value
|
|
50
|
-
when
|
|
51
|
-
when
|
|
73
|
+
when Float then @sql << value.to_s
|
|
74
|
+
when Integer then @sql << value.to_s
|
|
75
|
+
when String then @sql << Sanitizer.value_wrap(value)
|
|
52
76
|
end
|
|
53
77
|
end
|
|
54
78
|
end
|
|
79
|
+
|
|
55
80
|
end
|
|
81
|
+
|
|
56
82
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds one or more INSERT statements from a Hash of table → params.
|
|
3
4
|
#
|
|
4
5
|
# Usage (single row):
|
|
@@ -13,23 +14,35 @@ module Json2sql
|
|
|
13
14
|
# { "columns" => { "name" => "rails" } }
|
|
14
15
|
# ]
|
|
15
16
|
# )
|
|
17
|
+
|
|
16
18
|
class InsertRunner
|
|
19
|
+
|
|
17
20
|
def self.build(input)
|
|
21
|
+
|
|
22
|
+
sql = +""
|
|
23
|
+
|
|
18
24
|
input = Json2sql.normalize(input)
|
|
19
|
-
sql = +""
|
|
20
25
|
|
|
21
26
|
input.each do |table, value|
|
|
27
|
+
|
|
22
28
|
tbl = table.to_s
|
|
23
29
|
|
|
24
30
|
case value
|
|
31
|
+
|
|
25
32
|
when Hash
|
|
33
|
+
|
|
26
34
|
InsertModel.new(sql, tbl).build(value)
|
|
35
|
+
|
|
27
36
|
sql << ";\n"
|
|
37
|
+
|
|
28
38
|
when Array
|
|
39
|
+
|
|
29
40
|
value.each do |item|
|
|
41
|
+
|
|
30
42
|
next unless item.is_a?(Hash)
|
|
31
43
|
|
|
32
44
|
InsertModel.new(sql, tbl).build(item)
|
|
45
|
+
|
|
33
46
|
sql << ";\n"
|
|
34
47
|
end
|
|
35
48
|
end
|
|
@@ -37,5 +50,7 @@ module Json2sql
|
|
|
37
50
|
|
|
38
51
|
sql
|
|
39
52
|
end
|
|
53
|
+
|
|
40
54
|
end
|
|
55
|
+
|
|
41
56
|
end
|
data/lib/json2sql/sanitizer.rb
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
module Sanitizer
|
|
4
|
+
|
|
3
5
|
# Characters stripped from SQL identifiers (table/column names).
|
|
4
6
|
KEYWORD_DANGEROUS = /[ `;"'\\]/
|
|
5
7
|
|
|
6
8
|
# Removes dangerous characters from an identifier string.
|
|
7
9
|
def self.keyword(input)
|
|
10
|
+
|
|
8
11
|
input.to_s.gsub(KEYWORD_DANGEROUS, "")
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
# Escapes a value string for safe embedding between SQL quotes.
|
|
12
15
|
# ' → '' and \ → \\
|
|
13
16
|
def self.value(input)
|
|
17
|
+
|
|
14
18
|
input.to_s.gsub("\\", "\\\\\\\\").gsub("'", "''")
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
# Wraps an identifier in the given quote character (default: backtick).
|
|
18
22
|
# Dangerous characters inside the identifier are stripped.
|
|
19
23
|
def self.keyword_wrap(input, wrap = "`")
|
|
24
|
+
|
|
20
25
|
"#{wrap}#{keyword(input)}#{wrap}"
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
# Wraps a value in the given quote character (default: single-quote).
|
|
24
29
|
# Single quotes and backslashes inside the value are escaped.
|
|
25
30
|
def self.value_wrap(input, wrap = "'")
|
|
31
|
+
|
|
26
32
|
"#{wrap}#{value(input)}#{wrap}"
|
|
27
33
|
end
|
|
28
34
|
|
|
@@ -30,6 +36,7 @@ module Json2sql
|
|
|
30
36
|
# backtick-quoted SQL reference (e.g. "`users`.`id`").
|
|
31
37
|
# Strips the leading "$." and splits on ".".
|
|
32
38
|
def self.reference(input)
|
|
39
|
+
|
|
33
40
|
str = input.to_s[2..] # strip leading "$."
|
|
34
41
|
result = +"`"
|
|
35
42
|
str.each_char do |c|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds a SELECT SQL statement for a single table.
|
|
3
4
|
#
|
|
4
5
|
# Input Hash keys (all optional):
|
|
@@ -12,95 +13,147 @@ module Json2sql
|
|
|
12
13
|
# "options" => ["total"] – wrap response with data/total JSON
|
|
13
14
|
# "children" => { "table" => {...} } – nested child arrays
|
|
14
15
|
# "parents" => { "table" => {...} } – nested parent objects
|
|
16
|
+
|
|
15
17
|
class SelectModel
|
|
18
|
+
|
|
16
19
|
def initialize(sql, table, relation)
|
|
17
|
-
|
|
18
|
-
@
|
|
20
|
+
|
|
21
|
+
@sql = sql
|
|
22
|
+
|
|
23
|
+
@table = table.to_s
|
|
24
|
+
|
|
19
25
|
@relation = relation
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
# SELECT COUNT(*) AS `table` FROM `table` WHERE ...
|
|
29
|
+
|
|
23
30
|
def build_query_count(params)
|
|
31
|
+
|
|
24
32
|
@sql << "SELECT COUNT(*) AS "
|
|
33
|
+
|
|
25
34
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
35
|
+
|
|
26
36
|
@sql << " FROM "
|
|
37
|
+
|
|
27
38
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
39
|
+
|
|
28
40
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
# Plain SELECT col1, col2 FROM `table` WHERE ... ORDER BY ... LIMIT ... OFFSET ...
|
|
44
|
+
|
|
32
45
|
def build_query_default(params)
|
|
46
|
+
|
|
33
47
|
@sep = false
|
|
48
|
+
|
|
34
49
|
@sql << "SELECT "
|
|
50
|
+
|
|
35
51
|
build_columns_default(params)
|
|
52
|
+
|
|
36
53
|
@sql << " FROM "
|
|
54
|
+
|
|
37
55
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
56
|
+
|
|
38
57
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
39
|
-
|
|
58
|
+
|
|
59
|
+
build_order(params)
|
|
40
60
|
build_limit(params)
|
|
41
61
|
build_offset(params)
|
|
42
62
|
end
|
|
43
63
|
|
|
44
64
|
# SELECT JSON_ARRAYAGG(JSON_OBJECT(...)) AS `table`
|
|
45
65
|
# FROM LATERAL (SELECT * FROM `table` WHERE ... ORDER ... LIMIT ...) AS `table`
|
|
66
|
+
|
|
46
67
|
def build_query_array(params)
|
|
68
|
+
|
|
47
69
|
@sep = false
|
|
70
|
+
|
|
48
71
|
@sql << "SELECT JSON_ARRAYAGG(JSON_OBJECT("
|
|
72
|
+
|
|
49
73
|
build_columns_json(params)
|
|
50
74
|
build_columns_array(params)
|
|
51
75
|
build_columns_object(params)
|
|
76
|
+
|
|
52
77
|
@sql << ")) AS "
|
|
78
|
+
|
|
53
79
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
80
|
+
|
|
54
81
|
@sql << " FROM LATERAL (SELECT * FROM "
|
|
82
|
+
|
|
55
83
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
84
|
+
|
|
56
85
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
86
|
+
|
|
57
87
|
build_order(params)
|
|
58
88
|
build_limit(params)
|
|
59
89
|
build_offset(params)
|
|
90
|
+
|
|
60
91
|
@sql << ") AS "
|
|
92
|
+
|
|
61
93
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
62
94
|
end
|
|
63
95
|
|
|
64
96
|
# SELECT JSON_OBJECT(...) AS `table`
|
|
65
97
|
# FROM LATERAL (SELECT * FROM `table` WHERE ...) AS `table`
|
|
98
|
+
|
|
66
99
|
def build_query_object(params)
|
|
100
|
+
|
|
67
101
|
@sep = false
|
|
102
|
+
|
|
68
103
|
@sql << "SELECT JSON_OBJECT("
|
|
104
|
+
|
|
69
105
|
build_columns_json(params)
|
|
70
106
|
build_columns_array(params)
|
|
71
107
|
build_columns_object(params)
|
|
108
|
+
|
|
72
109
|
@sql << ") AS "
|
|
110
|
+
|
|
73
111
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
112
|
+
|
|
74
113
|
@sql << " FROM LATERAL (SELECT * FROM "
|
|
114
|
+
|
|
75
115
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
116
|
+
|
|
76
117
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
118
|
+
|
|
77
119
|
build_order(params)
|
|
78
120
|
build_limit(params)
|
|
79
121
|
build_offset(params)
|
|
122
|
+
|
|
80
123
|
@sql << ") AS "
|
|
124
|
+
|
|
81
125
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
82
126
|
end
|
|
83
127
|
|
|
84
128
|
# Smart dispatcher:
|
|
85
129
|
# - no options → build_query_array
|
|
86
130
|
# - options includes "total" → wraps with JSON_OBJECT('data', ..., 'total', COUNT(*))
|
|
131
|
+
|
|
87
132
|
def build_query_options(params)
|
|
133
|
+
|
|
88
134
|
options = params["options"]
|
|
89
135
|
|
|
90
136
|
unless options.is_a?(Array) && !options.empty?
|
|
137
|
+
|
|
91
138
|
build_query_array(params)
|
|
139
|
+
|
|
92
140
|
return
|
|
93
141
|
end
|
|
94
142
|
|
|
95
143
|
total = options.include?("total")
|
|
96
144
|
|
|
97
145
|
@sql << "SELECT JSON_OBJECT('data', ("
|
|
146
|
+
|
|
98
147
|
build_query_array(params)
|
|
148
|
+
|
|
99
149
|
@sql << ")"
|
|
100
150
|
|
|
101
151
|
if total
|
|
152
|
+
|
|
102
153
|
@sql << ", 'total', ("
|
|
154
|
+
|
|
103
155
|
build_query_count(params)
|
|
156
|
+
|
|
104
157
|
@sql << ")"
|
|
105
158
|
end
|
|
106
159
|
|
|
@@ -113,93 +166,137 @@ module Json2sql
|
|
|
113
166
|
# comma placement across multiple calls within a single query build.
|
|
114
167
|
|
|
115
168
|
# Appends plain column references: `table`.`col`, `table`.`col2`, ...
|
|
169
|
+
|
|
116
170
|
def build_columns_default(params)
|
|
171
|
+
|
|
117
172
|
columns = params["columns"]
|
|
173
|
+
|
|
118
174
|
return unless columns.is_a?(Array)
|
|
119
175
|
|
|
120
176
|
columns.each do |column|
|
|
177
|
+
|
|
121
178
|
next unless column.is_a?(String) || column.is_a?(Symbol)
|
|
122
179
|
|
|
123
180
|
@sql << ", " if @sep
|
|
181
|
+
|
|
124
182
|
@sep = true
|
|
183
|
+
|
|
125
184
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
185
|
+
|
|
126
186
|
@sql << Sanitizer.keyword_wrap(column.to_s)
|
|
127
187
|
end
|
|
128
188
|
end
|
|
129
189
|
|
|
130
190
|
# Appends JSON key-value pairs for columns: 'col', `table`.`col`, ...
|
|
191
|
+
|
|
131
192
|
def build_columns_json(params)
|
|
193
|
+
|
|
132
194
|
columns = params["columns"]
|
|
195
|
+
|
|
133
196
|
return unless columns.is_a?(Array)
|
|
134
197
|
|
|
135
198
|
columns.each do |column|
|
|
199
|
+
|
|
136
200
|
next unless column.is_a?(String) || column.is_a?(Symbol)
|
|
137
201
|
|
|
138
202
|
@sql << ", " if @sep
|
|
203
|
+
|
|
139
204
|
@sep = true
|
|
205
|
+
|
|
140
206
|
col = column.to_s
|
|
207
|
+
|
|
141
208
|
@sql << Sanitizer.keyword_wrap(col, "'")
|
|
209
|
+
|
|
142
210
|
@sql << ", "
|
|
211
|
+
|
|
143
212
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
213
|
+
|
|
144
214
|
@sql << Sanitizer.keyword_wrap(col)
|
|
145
215
|
end
|
|
146
216
|
end
|
|
147
217
|
|
|
148
218
|
# Appends nested child arrays (subquery → JSON_ARRAYAGG).
|
|
149
219
|
# Uses WhereRelation::PARENT because child table references parent.
|
|
220
|
+
|
|
150
221
|
def build_columns_array(params)
|
|
222
|
+
|
|
151
223
|
children = params["children"]
|
|
224
|
+
|
|
152
225
|
return unless children.is_a?(Hash)
|
|
153
226
|
|
|
154
227
|
relation = WhereRelation.parent(@table)
|
|
155
228
|
|
|
156
229
|
children.each do |key, value|
|
|
230
|
+
|
|
157
231
|
next unless value.is_a?(Hash)
|
|
158
232
|
|
|
159
233
|
@sql << ", " if @sep
|
|
234
|
+
|
|
160
235
|
@sep = true
|
|
236
|
+
|
|
161
237
|
tbl = key.to_s
|
|
238
|
+
|
|
162
239
|
@sql << Sanitizer.keyword_wrap(tbl, "'")
|
|
240
|
+
|
|
163
241
|
@sql << ", ("
|
|
242
|
+
|
|
164
243
|
SelectModel.new(@sql, tbl, relation).build_query_options(value)
|
|
244
|
+
|
|
165
245
|
@sql << ")"
|
|
166
246
|
end
|
|
167
247
|
end
|
|
168
248
|
|
|
169
249
|
# Appends nested parent objects (subquery → JSON_OBJECT, single row).
|
|
170
250
|
# Uses WhereRelation::CHILD because parent table is referenced from child.
|
|
251
|
+
|
|
171
252
|
def build_columns_object(params)
|
|
253
|
+
|
|
172
254
|
parents = params["parents"]
|
|
255
|
+
|
|
173
256
|
return unless parents.is_a?(Hash)
|
|
174
257
|
|
|
175
258
|
relation = WhereRelation.child(@table)
|
|
176
259
|
|
|
177
260
|
parents.each do |key, value|
|
|
261
|
+
|
|
178
262
|
next unless value.is_a?(Hash)
|
|
179
263
|
|
|
180
264
|
@sql << ", " if @sep
|
|
265
|
+
|
|
181
266
|
@sep = true
|
|
267
|
+
|
|
182
268
|
tbl = key.to_s
|
|
269
|
+
|
|
183
270
|
@sql << Sanitizer.keyword_wrap(tbl, "'")
|
|
271
|
+
|
|
184
272
|
@sql << ", ("
|
|
273
|
+
|
|
185
274
|
SelectModel.new(@sql, tbl, relation).build_query_object(value)
|
|
275
|
+
|
|
186
276
|
@sql << ")"
|
|
187
277
|
end
|
|
188
278
|
end
|
|
189
279
|
|
|
190
280
|
def build_order(params)
|
|
281
|
+
|
|
191
282
|
order = params["order"]
|
|
283
|
+
|
|
192
284
|
return unless order.is_a?(Hash) && !order.empty?
|
|
193
285
|
|
|
194
286
|
@sql << " ORDER BY "
|
|
287
|
+
|
|
195
288
|
glue = false
|
|
196
289
|
|
|
197
290
|
order.each do |key, value|
|
|
291
|
+
|
|
198
292
|
@sql << ", " if glue
|
|
293
|
+
|
|
199
294
|
glue = true
|
|
200
295
|
|
|
201
296
|
column = key.to_s
|
|
297
|
+
|
|
202
298
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
299
|
+
|
|
203
300
|
@sql << Sanitizer.keyword_wrap(column)
|
|
204
301
|
|
|
205
302
|
case value.to_s.downcase
|
|
@@ -210,13 +307,19 @@ module Json2sql
|
|
|
210
307
|
end
|
|
211
308
|
|
|
212
309
|
def build_limit(params)
|
|
310
|
+
|
|
213
311
|
limit = params["limit"]
|
|
312
|
+
|
|
214
313
|
@sql << " LIMIT #{limit}" if limit.is_a?(Integer)
|
|
215
314
|
end
|
|
216
315
|
|
|
217
316
|
def build_offset(params)
|
|
317
|
+
|
|
218
318
|
offset = params["offset"]
|
|
319
|
+
|
|
219
320
|
@sql << " OFFSET #{offset}" if offset.is_a?(Integer)
|
|
220
321
|
end
|
|
322
|
+
|
|
221
323
|
end
|
|
324
|
+
|
|
222
325
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds a top-level SELECT statement from a Hash of table → params.
|
|
3
4
|
#
|
|
4
5
|
# Usage:
|
|
@@ -16,29 +17,43 @@ module Json2sql
|
|
|
16
17
|
# Output wraps every table in JSON_OBJECT so the client receives a single
|
|
17
18
|
# JSON document:
|
|
18
19
|
# SELECT JSON_OBJECT('users', (...));
|
|
20
|
+
|
|
19
21
|
class SelectRunner
|
|
22
|
+
|
|
20
23
|
def self.build(input)
|
|
21
|
-
|
|
22
|
-
sql
|
|
24
|
+
|
|
25
|
+
sql = +""
|
|
26
|
+
|
|
23
27
|
separator = false
|
|
24
|
-
|
|
28
|
+
|
|
29
|
+
input = Json2sql.normalize(input)
|
|
30
|
+
|
|
31
|
+
relation = WhereRelation.none("")
|
|
25
32
|
|
|
26
33
|
sql << "SELECT JSON_OBJECT("
|
|
27
34
|
|
|
28
35
|
input.each do |table, value|
|
|
36
|
+
|
|
29
37
|
next unless value.is_a?(Hash)
|
|
30
38
|
|
|
31
39
|
sql << ", " if separator
|
|
40
|
+
|
|
32
41
|
separator = true
|
|
33
42
|
|
|
34
43
|
sql << Sanitizer.keyword_wrap(table.to_s, "'")
|
|
44
|
+
|
|
35
45
|
sql << ", ("
|
|
46
|
+
|
|
36
47
|
SelectModel.new(sql, table.to_s, relation).build_query_options(value)
|
|
48
|
+
|
|
37
49
|
sql << ")"
|
|
38
50
|
end
|
|
39
51
|
|
|
40
52
|
sql << ");\n"
|
|
53
|
+
|
|
41
54
|
sql
|
|
42
55
|
end
|
|
56
|
+
|
|
43
57
|
end
|
|
58
|
+
|
|
44
59
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds an UPDATE statement for a single table.
|
|
3
4
|
#
|
|
4
5
|
# Input Hash:
|
|
@@ -7,42 +8,63 @@ module Json2sql
|
|
|
7
8
|
# "or" => { ... } – WHERE conditions (OR)
|
|
8
9
|
#
|
|
9
10
|
# Value types follow the same rules as InsertModel.
|
|
11
|
+
|
|
10
12
|
class UpdateModel
|
|
13
|
+
|
|
11
14
|
def initialize(sql, table, relation)
|
|
12
|
-
|
|
13
|
-
@
|
|
15
|
+
|
|
16
|
+
@sql = sql
|
|
17
|
+
|
|
18
|
+
@table = table.to_s
|
|
19
|
+
|
|
14
20
|
@relation = relation
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
def build(params)
|
|
24
|
+
|
|
18
25
|
@sql << "UPDATE "
|
|
26
|
+
|
|
19
27
|
@sql << Sanitizer.keyword_wrap(@table)
|
|
28
|
+
|
|
20
29
|
@sql << " SET "
|
|
30
|
+
|
|
21
31
|
build_columns(params)
|
|
32
|
+
|
|
22
33
|
WhereModel.new(@sql, @table, @relation).build(params)
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
private
|
|
26
37
|
|
|
27
38
|
def build_columns(params)
|
|
39
|
+
|
|
28
40
|
columns = params["columns"]
|
|
41
|
+
|
|
29
42
|
return unless columns.is_a?(Hash)
|
|
30
43
|
|
|
31
44
|
separator = false
|
|
45
|
+
|
|
32
46
|
columns.each do |key, value|
|
|
47
|
+
|
|
33
48
|
@sql << ", " if separator
|
|
49
|
+
|
|
34
50
|
separator = true
|
|
35
51
|
|
|
36
52
|
column = key.to_s
|
|
53
|
+
|
|
37
54
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
55
|
+
|
|
38
56
|
@sql << Sanitizer.keyword_wrap(column)
|
|
57
|
+
|
|
39
58
|
@sql << " = "
|
|
40
59
|
|
|
41
60
|
case value
|
|
42
|
-
when
|
|
43
|
-
when
|
|
61
|
+
when Float then @sql << value.to_s
|
|
62
|
+
when Integer then @sql << value.to_s
|
|
63
|
+
when String then @sql << Sanitizer.value_wrap(value)
|
|
44
64
|
end
|
|
45
65
|
end
|
|
46
66
|
end
|
|
67
|
+
|
|
47
68
|
end
|
|
69
|
+
|
|
48
70
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds one or more UPDATE statements from a Hash of table → params.
|
|
3
4
|
#
|
|
4
5
|
# Usage (single row):
|
|
@@ -16,24 +17,37 @@ module Json2sql
|
|
|
16
17
|
# { "columns" => { "value" => "en" }, "and" => { "key" => "lang" } }
|
|
17
18
|
# ]
|
|
18
19
|
# )
|
|
20
|
+
|
|
19
21
|
class UpdateRunner
|
|
22
|
+
|
|
20
23
|
def self.build(input)
|
|
21
|
-
|
|
22
|
-
sql
|
|
24
|
+
|
|
25
|
+
sql = +""
|
|
26
|
+
|
|
27
|
+
input = Json2sql.normalize(input)
|
|
28
|
+
|
|
23
29
|
relation = WhereRelation.none("")
|
|
24
30
|
|
|
25
31
|
input.each do |table, value|
|
|
32
|
+
|
|
26
33
|
tbl = table.to_s
|
|
27
34
|
|
|
28
35
|
case value
|
|
36
|
+
|
|
29
37
|
when Hash
|
|
38
|
+
|
|
30
39
|
UpdateModel.new(sql, tbl, relation).build(value)
|
|
40
|
+
|
|
31
41
|
sql << ";\n"
|
|
42
|
+
|
|
32
43
|
when Array
|
|
44
|
+
|
|
33
45
|
value.each do |item|
|
|
46
|
+
|
|
34
47
|
next unless item.is_a?(Hash)
|
|
35
48
|
|
|
36
49
|
UpdateModel.new(sql, tbl, relation).build(item)
|
|
50
|
+
|
|
37
51
|
sql << ";\n"
|
|
38
52
|
end
|
|
39
53
|
end
|
|
@@ -41,5 +55,7 @@ module Json2sql
|
|
|
41
55
|
|
|
42
56
|
sql
|
|
43
57
|
end
|
|
58
|
+
|
|
44
59
|
end
|
|
60
|
+
|
|
45
61
|
end
|
data/lib/json2sql/version.rb
CHANGED
data/lib/json2sql/where_model.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
# Builds a SQL WHERE clause from a Hash describing the conditions.
|
|
3
4
|
#
|
|
4
5
|
# Input structure mirrors the JSON format used in the C++ backend:
|
|
@@ -18,35 +19,46 @@ module Json2sql
|
|
|
18
19
|
# Supported operators: = < > <= >= != <>
|
|
19
20
|
# in !in like !like
|
|
20
21
|
# String pseudo-actions: contains (LIKE %v%), first (LIKE v%), last (LIKE %v)
|
|
22
|
+
|
|
21
23
|
class WhereModel
|
|
24
|
+
|
|
22
25
|
def initialize(sql, table, relation)
|
|
23
|
-
|
|
24
|
-
@
|
|
26
|
+
|
|
27
|
+
@sql = sql
|
|
28
|
+
|
|
29
|
+
@table = table.to_s
|
|
30
|
+
|
|
25
31
|
@relation = relation
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def build(params)
|
|
35
|
+
|
|
29
36
|
has_relation = @relation.kind != WhereRelation::NONE
|
|
37
|
+
|
|
30
38
|
has_where_and = params["and"].is_a?(Hash)
|
|
39
|
+
|
|
31
40
|
has_where_or = params["or"].is_a?(Hash)
|
|
32
41
|
|
|
33
42
|
return unless has_relation || has_where_and || has_where_or
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
parts = []
|
|
36
45
|
|
|
37
|
-
if has_relation
|
|
38
|
-
@relation.build_table_relation(@sql, @table)
|
|
39
|
-
@sql << " AND " if has_where_and || has_where_or
|
|
40
|
-
end
|
|
46
|
+
parts << with_buffer { @relation.build_table_relation(@sql, @table) } if has_relation
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
48
|
+
scope = has_where_and ? " AND " : " OR "
|
|
49
|
+
|
|
50
|
+
group = params["and"] || params["or"]
|
|
46
51
|
|
|
47
|
-
if
|
|
48
|
-
|
|
52
|
+
if group
|
|
53
|
+
|
|
54
|
+
frag = build_column_group(group, scope)
|
|
55
|
+
|
|
56
|
+
parts << frag unless frag.empty?
|
|
49
57
|
end
|
|
58
|
+
|
|
59
|
+
return if parts.empty?
|
|
60
|
+
|
|
61
|
+
@sql << " WHERE " << parts.join(" AND ")
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
private
|
|
@@ -56,36 +68,58 @@ module Json2sql
|
|
|
56
68
|
# -------------------------------------------------------------------------
|
|
57
69
|
|
|
58
70
|
def build_column_group(params, scope)
|
|
59
|
-
@sql << "("
|
|
60
|
-
glue = false
|
|
61
71
|
|
|
62
|
-
params.
|
|
63
|
-
@sql << scope if glue
|
|
64
|
-
glue = true
|
|
72
|
+
fragments = params.filter_map do |key, value|
|
|
65
73
|
|
|
66
|
-
build_column_types(value, scope, key.to_s)
|
|
74
|
+
frag = with_buffer { build_column_types(value, scope, key.to_s) }
|
|
75
|
+
|
|
76
|
+
frag.empty? ? nil : frag
|
|
67
77
|
end
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
return "" if fragments.empty?
|
|
80
|
+
|
|
81
|
+
"(" + fragments.join(scope) + ")"
|
|
70
82
|
end
|
|
71
83
|
|
|
72
84
|
# Dispatch by Ruby type of the value.
|
|
85
|
+
|
|
73
86
|
def build_column_types(params, scope, column)
|
|
87
|
+
|
|
74
88
|
case params
|
|
89
|
+
|
|
75
90
|
when TrueClass, FalseClass
|
|
91
|
+
|
|
76
92
|
build_action_types(params, column, "=")
|
|
93
|
+
|
|
77
94
|
when Integer
|
|
95
|
+
|
|
78
96
|
build_action_types(params, column, "=")
|
|
97
|
+
|
|
79
98
|
when String
|
|
99
|
+
|
|
80
100
|
build_action_types(params, column, "contains")
|
|
101
|
+
|
|
81
102
|
when Hash
|
|
103
|
+
|
|
82
104
|
if column == "and"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
|
|
106
|
+
frag = build_column_group(params, " AND ")
|
|
107
|
+
|
|
108
|
+
@sql << frag unless frag.empty?
|
|
109
|
+
|
|
110
|
+
return
|
|
88
111
|
end
|
|
112
|
+
|
|
113
|
+
if column == "or"
|
|
114
|
+
|
|
115
|
+
frag = build_column_group(params, " OR ")
|
|
116
|
+
|
|
117
|
+
@sql << frag unless frag.empty?
|
|
118
|
+
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
build_action_group(params, scope, column)
|
|
89
123
|
end
|
|
90
124
|
end
|
|
91
125
|
|
|
@@ -94,24 +128,30 @@ module Json2sql
|
|
|
94
128
|
# -------------------------------------------------------------------------
|
|
95
129
|
|
|
96
130
|
def build_action_group(params, scope, column)
|
|
97
|
-
glue = false
|
|
98
131
|
|
|
99
|
-
params.
|
|
100
|
-
@sql << scope if glue
|
|
101
|
-
glue = true
|
|
132
|
+
fragments = params.filter_map do |key, value|
|
|
102
133
|
|
|
103
|
-
build_action_types(value, column, key.to_s)
|
|
134
|
+
frag = with_buffer { build_action_types(value, column, key.to_s) }
|
|
135
|
+
|
|
136
|
+
frag.empty? ? nil : frag
|
|
104
137
|
end
|
|
138
|
+
|
|
139
|
+
@sql << fragments.join(scope) unless fragments.empty?
|
|
105
140
|
end
|
|
106
141
|
|
|
107
142
|
def build_action_types(params, column, action)
|
|
143
|
+
|
|
108
144
|
if action == "and"
|
|
145
|
+
|
|
109
146
|
build_column_types(params, " AND ", column)
|
|
147
|
+
|
|
110
148
|
return
|
|
111
149
|
end
|
|
112
150
|
|
|
113
151
|
if action == "or"
|
|
152
|
+
|
|
114
153
|
build_column_types(params, " OR ", column)
|
|
154
|
+
|
|
115
155
|
return
|
|
116
156
|
end
|
|
117
157
|
|
|
@@ -123,81 +163,123 @@ module Json2sql
|
|
|
123
163
|
# -------------------------------------------------------------------------
|
|
124
164
|
|
|
125
165
|
def build_action_values(params, column, action) # rubocop:disable Metrics/MethodLength
|
|
166
|
+
|
|
126
167
|
case params
|
|
168
|
+
|
|
127
169
|
when TrueClass, FalseClass
|
|
170
|
+
|
|
128
171
|
# Only "null" → IS NULL / IS NOT NULL. Boolean equality is not emitted
|
|
129
172
|
# (matches C++ behaviour — use integer 1/0 for boolean equality).
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
return unless action == "null"
|
|
174
|
+
|
|
175
|
+
action_str = params ? " IS " : " IS NOT "
|
|
176
|
+
|
|
177
|
+
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
178
|
+
|
|
179
|
+
@sql << Sanitizer.keyword_wrap(column)
|
|
180
|
+
|
|
181
|
+
@sql << action_str << "NULL"
|
|
136
182
|
|
|
137
183
|
when Integer
|
|
184
|
+
|
|
138
185
|
action_name = get_action(action)
|
|
186
|
+
|
|
139
187
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
188
|
+
|
|
140
189
|
@sql << Sanitizer.keyword_wrap(column)
|
|
190
|
+
|
|
141
191
|
@sql << " #{action_name} #{params}"
|
|
142
192
|
|
|
143
193
|
when Float
|
|
194
|
+
|
|
144
195
|
action_name = get_action(action)
|
|
196
|
+
|
|
145
197
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
198
|
+
|
|
146
199
|
@sql << Sanitizer.keyword_wrap(column)
|
|
200
|
+
|
|
147
201
|
@sql << " #{action_name} #{params}"
|
|
148
202
|
|
|
149
203
|
when String
|
|
204
|
+
|
|
150
205
|
build_action_string(params, column, action)
|
|
151
206
|
|
|
152
207
|
when Array
|
|
208
|
+
|
|
153
209
|
action_name = get_action(action)
|
|
210
|
+
|
|
154
211
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
212
|
+
|
|
155
213
|
@sql << Sanitizer.keyword_wrap(column)
|
|
214
|
+
|
|
156
215
|
@sql << " #{action_name} ("
|
|
216
|
+
|
|
157
217
|
build_array(params)
|
|
218
|
+
|
|
158
219
|
@sql << ")"
|
|
159
220
|
|
|
160
221
|
when Hash
|
|
222
|
+
|
|
161
223
|
action_name = get_action(action)
|
|
224
|
+
|
|
162
225
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
226
|
+
|
|
163
227
|
@sql << Sanitizer.keyword_wrap(column)
|
|
228
|
+
|
|
164
229
|
@sql << " #{action_name} ("
|
|
230
|
+
|
|
165
231
|
build_object(params)
|
|
232
|
+
|
|
166
233
|
@sql << ")"
|
|
167
234
|
end
|
|
168
235
|
end
|
|
169
236
|
|
|
170
237
|
def build_action_string(params, column, action)
|
|
238
|
+
|
|
171
239
|
action_name = get_action(action)
|
|
172
240
|
|
|
173
241
|
case action_name
|
|
242
|
+
|
|
174
243
|
when "last"
|
|
244
|
+
|
|
175
245
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
246
|
+
|
|
176
247
|
@sql << Sanitizer.keyword_wrap(column)
|
|
248
|
+
|
|
177
249
|
@sql << " LIKE '%" << Sanitizer.value(params) << "'"
|
|
178
250
|
|
|
179
251
|
when "first"
|
|
252
|
+
|
|
180
253
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
254
|
+
|
|
181
255
|
@sql << Sanitizer.keyword_wrap(column)
|
|
256
|
+
|
|
182
257
|
@sql << " LIKE '" << Sanitizer.value(params) << "%'"
|
|
183
258
|
|
|
184
259
|
when "contains"
|
|
260
|
+
|
|
185
261
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
262
|
+
|
|
186
263
|
@sql << Sanitizer.keyword_wrap(column)
|
|
264
|
+
|
|
187
265
|
@sql << " LIKE '%" << Sanitizer.value(params) << "%'"
|
|
188
266
|
|
|
189
267
|
else
|
|
268
|
+
|
|
269
|
+
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
270
|
+
|
|
271
|
+
@sql << Sanitizer.keyword_wrap(column)
|
|
272
|
+
|
|
273
|
+
@sql << " #{action_name} "
|
|
274
|
+
|
|
190
275
|
if params.start_with?("$.")
|
|
191
|
-
|
|
192
|
-
@sql << Sanitizer.keyword_wrap(column)
|
|
193
|
-
@sql << " #{action_name} "
|
|
276
|
+
|
|
194
277
|
@sql << Sanitizer.reference(params)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@sql << Sanitizer.keyword_wrap(column)
|
|
198
|
-
@sql << " #{action_name} "
|
|
199
|
-
@sql << Sanitizer.value_wrap(params)
|
|
278
|
+
|
|
279
|
+
return
|
|
200
280
|
end
|
|
281
|
+
|
|
282
|
+
@sql << Sanitizer.value_wrap(params)
|
|
201
283
|
end
|
|
202
284
|
end
|
|
203
285
|
|
|
@@ -206,49 +288,87 @@ module Json2sql
|
|
|
206
288
|
# -------------------------------------------------------------------------
|
|
207
289
|
|
|
208
290
|
def build_array(array)
|
|
291
|
+
|
|
209
292
|
if array.empty?
|
|
293
|
+
|
|
210
294
|
@sql << "NULL"
|
|
295
|
+
|
|
211
296
|
return
|
|
212
297
|
end
|
|
213
298
|
|
|
214
299
|
glue = false
|
|
300
|
+
|
|
215
301
|
array.each do |item|
|
|
302
|
+
|
|
216
303
|
@sql << ", " if glue
|
|
304
|
+
|
|
217
305
|
glue = true
|
|
218
306
|
|
|
219
307
|
case item
|
|
220
|
-
when
|
|
221
|
-
when
|
|
308
|
+
when Float then @sql << item.to_s
|
|
309
|
+
when Integer then @sql << item.to_s
|
|
310
|
+
when String then @sql << Sanitizer.value_wrap(item)
|
|
222
311
|
end
|
|
223
312
|
end
|
|
224
313
|
end
|
|
225
314
|
|
|
226
315
|
# Builds a UNION of sub-SELECTs (used when action value is a Hash of tables).
|
|
316
|
+
|
|
227
317
|
def build_object(object)
|
|
318
|
+
|
|
228
319
|
if object.empty?
|
|
320
|
+
|
|
229
321
|
@sql << "NULL"
|
|
322
|
+
|
|
230
323
|
return
|
|
231
324
|
end
|
|
232
325
|
|
|
233
326
|
glue = false
|
|
327
|
+
|
|
234
328
|
relation = WhereRelation.none(@table)
|
|
235
329
|
|
|
236
330
|
object.each do |key, value|
|
|
331
|
+
|
|
237
332
|
@sql << " UNION " if glue
|
|
333
|
+
|
|
238
334
|
glue = true
|
|
239
335
|
|
|
240
336
|
tbl = key.to_s
|
|
337
|
+
|
|
241
338
|
@sql << "("
|
|
339
|
+
|
|
242
340
|
SelectModel.new(@sql, tbl, relation).build_query_default(value)
|
|
341
|
+
|
|
243
342
|
@sql << ")"
|
|
244
343
|
end
|
|
245
344
|
end
|
|
246
345
|
|
|
346
|
+
# -------------------------------------------------------------------------
|
|
347
|
+
# Buffer helper — temporarily swap @sql for a fresh string so that any
|
|
348
|
+
# downstream << calls are captured in isolation. Returns the captured fragment.
|
|
349
|
+
# -------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def with_buffer
|
|
352
|
+
|
|
353
|
+
saved = @sql
|
|
354
|
+
|
|
355
|
+
@sql = +""
|
|
356
|
+
|
|
357
|
+
yield
|
|
358
|
+
|
|
359
|
+
@sql
|
|
360
|
+
|
|
361
|
+
ensure
|
|
362
|
+
|
|
363
|
+
@sql = saved
|
|
364
|
+
end
|
|
365
|
+
|
|
247
366
|
# -------------------------------------------------------------------------
|
|
248
367
|
# Operator mapping
|
|
249
368
|
# -------------------------------------------------------------------------
|
|
250
369
|
|
|
251
370
|
def get_action(action)
|
|
371
|
+
|
|
252
372
|
case action
|
|
253
373
|
when "=", "<", ">", "<=", ">=", "!=", "<>" then action
|
|
254
374
|
when "in" then "IN"
|
|
@@ -258,5 +378,7 @@ module Json2sql
|
|
|
258
378
|
else action
|
|
259
379
|
end
|
|
260
380
|
end
|
|
381
|
+
|
|
261
382
|
end
|
|
383
|
+
|
|
262
384
|
end
|
|
@@ -1,54 +1,79 @@
|
|
|
1
1
|
module Json2sql
|
|
2
|
+
|
|
2
3
|
class WhereRelation
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
NONE = :none
|
|
6
|
+
CHILD = :child
|
|
5
7
|
PARENT = :parent
|
|
6
8
|
|
|
7
9
|
attr_reader :table, :kind
|
|
8
10
|
|
|
9
11
|
def initialize(table, kind)
|
|
12
|
+
|
|
10
13
|
@table = table.to_s
|
|
14
|
+
|
|
11
15
|
@kind = kind
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
# Factory: no relationship (top-level query).
|
|
19
|
+
|
|
15
20
|
def self.none(table)
|
|
21
|
+
|
|
16
22
|
new(table, NONE)
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
# Factory: foreign key is on the child table pointing to the parent.
|
|
20
26
|
# Produces: `parent`.`child_id` = `current`.`id`
|
|
27
|
+
|
|
21
28
|
def self.child(table)
|
|
29
|
+
|
|
22
30
|
new(table, CHILD)
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
# Factory: foreign key is on the current/parent table pointing to the child.
|
|
26
34
|
# Produces: `current`.`parent_id` = `parent`.`id`
|
|
35
|
+
|
|
27
36
|
def self.parent(table)
|
|
37
|
+
|
|
28
38
|
new(table, PARENT)
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
# Appends the JOIN condition for this relationship into sql.
|
|
32
42
|
# +current+ is the name of the table being queried.
|
|
43
|
+
|
|
33
44
|
def build_table_relation(sql, current)
|
|
45
|
+
|
|
34
46
|
current = current.to_s
|
|
35
47
|
|
|
36
48
|
if kind == CHILD
|
|
49
|
+
|
|
37
50
|
sql << Sanitizer.keyword_wrap(table)
|
|
51
|
+
|
|
38
52
|
sql << "."
|
|
53
|
+
|
|
39
54
|
sql << build_table_id(current)
|
|
55
|
+
|
|
40
56
|
sql << " = "
|
|
57
|
+
|
|
41
58
|
sql << Sanitizer.keyword_wrap(current)
|
|
59
|
+
|
|
42
60
|
sql << ".`id`"
|
|
61
|
+
|
|
43
62
|
return
|
|
44
63
|
end
|
|
45
64
|
|
|
46
65
|
if kind == PARENT
|
|
66
|
+
|
|
47
67
|
sql << Sanitizer.keyword_wrap(current)
|
|
68
|
+
|
|
48
69
|
sql << "."
|
|
70
|
+
|
|
49
71
|
sql << build_table_id(table)
|
|
72
|
+
|
|
50
73
|
sql << " = "
|
|
74
|
+
|
|
51
75
|
sql << Sanitizer.keyword_wrap(table)
|
|
76
|
+
|
|
52
77
|
sql << ".`id`"
|
|
53
78
|
end
|
|
54
79
|
end
|
|
@@ -58,19 +83,18 @@ module Json2sql
|
|
|
58
83
|
# "users" → "`user_id`"
|
|
59
84
|
# "categories" → "`category_id`"
|
|
60
85
|
# "admins" → "`admin_id`"
|
|
86
|
+
|
|
61
87
|
def build_table_id(tbl)
|
|
62
|
-
|
|
63
|
-
base = Sanitizer.keyword(tbl)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
base
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
"`#{name}_id`"
|
|
88
|
+
|
|
89
|
+
base = Sanitizer.keyword(tbl.to_s)
|
|
90
|
+
|
|
91
|
+
return "`#{base[0..-4]}y_id`" if base.end_with?("ies")
|
|
92
|
+
|
|
93
|
+
return "`#{base[0..-2]}_id`" if base.end_with?("s")
|
|
94
|
+
|
|
95
|
+
"`#{base}_id`"
|
|
74
96
|
end
|
|
97
|
+
|
|
75
98
|
end
|
|
99
|
+
|
|
76
100
|
end
|
data/lib/json2sql.rb
CHANGED
|
@@ -22,17 +22,19 @@ require_relative "json2sql/delete_runner"
|
|
|
22
22
|
# Json2sql::InsertRunner.build(hash) → String
|
|
23
23
|
# Json2sql::UpdateRunner.build(hash) → String
|
|
24
24
|
# Json2sql::DeleteRunner.build(hash) → String
|
|
25
|
+
|
|
25
26
|
module Json2sql
|
|
27
|
+
|
|
26
28
|
# Deep-converts all Hash keys to Strings and recurses into nested Hashes
|
|
27
29
|
# and Arrays. Leaves all other values (Integers, Strings, etc.) unchanged.
|
|
30
|
+
|
|
28
31
|
def self.normalize(obj)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
obj
|
|
36
|
-
end
|
|
32
|
+
|
|
33
|
+
return obj.each_with_object({}) { |(k, v), h| h[k.to_s] = normalize(v) } if obj.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
return obj.map { |v| normalize(v) } if obj.is_a?(Array)
|
|
36
|
+
|
|
37
|
+
obj
|
|
37
38
|
end
|
|
39
|
+
|
|
38
40
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json2sql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago da Silva
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|