json2sql 1.0.0 → 1.0.1
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 +111 -5
- 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: 3caea0aaaaac6b6bdc5fe29341cf711d7ce2c34a0752fd06bfd246eacfc4c1df
|
|
4
|
+
data.tar.gz: 49341acb1ed445eaf48ed6dd0cee2fff492105f3f823d7e4d415671acf4208d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09d2615939602fe80edcec47e521ddf58678aab8c048336795129fdda56061d6a38d109bfc0a4321cfc05298c83df0884a79d097b4e5655e4b544b0f0a4d343b'
|
|
7
|
+
data.tar.gz: c46959b88dd70ea6f8981ab7ae6484281e77ec75d82dc06a197af7f9cdefdd5566522a1e050e5a910ab0f7463fbd5fdc12ef5705d1a08358d571596b98f7ebf5
|
|
@@ -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,16 +19,24 @@ 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
|
|
@@ -35,16 +44,21 @@ module Json2sql
|
|
|
35
44
|
@sql << " WHERE "
|
|
36
45
|
|
|
37
46
|
if has_relation
|
|
47
|
+
|
|
38
48
|
@relation.build_table_relation(@sql, @table)
|
|
49
|
+
|
|
39
50
|
@sql << " AND " if has_where_and || has_where_or
|
|
40
51
|
end
|
|
41
52
|
|
|
42
53
|
if has_where_and
|
|
54
|
+
|
|
43
55
|
build_column_group(params["and"], " AND ")
|
|
56
|
+
|
|
44
57
|
return
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
if has_where_or
|
|
61
|
+
|
|
48
62
|
build_column_group(params["or"], " OR ")
|
|
49
63
|
end
|
|
50
64
|
end
|
|
@@ -56,11 +70,15 @@ module Json2sql
|
|
|
56
70
|
# -------------------------------------------------------------------------
|
|
57
71
|
|
|
58
72
|
def build_column_group(params, scope)
|
|
73
|
+
|
|
59
74
|
@sql << "("
|
|
75
|
+
|
|
60
76
|
glue = false
|
|
61
77
|
|
|
62
78
|
params.each do |key, value|
|
|
79
|
+
|
|
63
80
|
@sql << scope if glue
|
|
81
|
+
|
|
64
82
|
glue = true
|
|
65
83
|
|
|
66
84
|
build_column_types(value, scope, key.to_s)
|
|
@@ -70,20 +88,33 @@ module Json2sql
|
|
|
70
88
|
end
|
|
71
89
|
|
|
72
90
|
# Dispatch by Ruby type of the value.
|
|
91
|
+
|
|
73
92
|
def build_column_types(params, scope, column)
|
|
93
|
+
|
|
74
94
|
case params
|
|
75
95
|
when TrueClass, FalseClass
|
|
96
|
+
|
|
76
97
|
build_action_types(params, column, "=")
|
|
98
|
+
|
|
77
99
|
when Integer
|
|
100
|
+
|
|
78
101
|
build_action_types(params, column, "=")
|
|
102
|
+
|
|
79
103
|
when String
|
|
104
|
+
|
|
80
105
|
build_action_types(params, column, "contains")
|
|
106
|
+
|
|
81
107
|
when Hash
|
|
108
|
+
|
|
82
109
|
if column == "and"
|
|
110
|
+
|
|
83
111
|
build_column_group(params, " AND ")
|
|
112
|
+
|
|
84
113
|
elsif column == "or"
|
|
114
|
+
|
|
85
115
|
build_column_group(params, " OR ")
|
|
86
|
-
|
|
116
|
+
|
|
117
|
+
else
|
|
87
118
|
build_action_group(params, scope, column)
|
|
88
119
|
end
|
|
89
120
|
end
|
|
@@ -94,10 +125,13 @@ module Json2sql
|
|
|
94
125
|
# -------------------------------------------------------------------------
|
|
95
126
|
|
|
96
127
|
def build_action_group(params, scope, column)
|
|
128
|
+
|
|
97
129
|
glue = false
|
|
98
130
|
|
|
99
131
|
params.each do |key, value|
|
|
132
|
+
|
|
100
133
|
@sql << scope if glue
|
|
134
|
+
|
|
101
135
|
glue = true
|
|
102
136
|
|
|
103
137
|
build_action_types(value, column, key.to_s)
|
|
@@ -105,13 +139,18 @@ module Json2sql
|
|
|
105
139
|
end
|
|
106
140
|
|
|
107
141
|
def build_action_types(params, column, action)
|
|
142
|
+
|
|
108
143
|
if action == "and"
|
|
144
|
+
|
|
109
145
|
build_column_types(params, " AND ", column)
|
|
146
|
+
|
|
110
147
|
return
|
|
111
148
|
end
|
|
112
149
|
|
|
113
150
|
if action == "or"
|
|
151
|
+
|
|
114
152
|
build_column_types(params, " OR ", column)
|
|
153
|
+
|
|
115
154
|
return
|
|
116
155
|
end
|
|
117
156
|
|
|
@@ -123,79 +162,126 @@ module Json2sql
|
|
|
123
162
|
# -------------------------------------------------------------------------
|
|
124
163
|
|
|
125
164
|
def build_action_values(params, column, action) # rubocop:disable Metrics/MethodLength
|
|
165
|
+
|
|
126
166
|
case params
|
|
127
167
|
when TrueClass, FalseClass
|
|
168
|
+
|
|
128
169
|
# Only "null" → IS NULL / IS NOT NULL. Boolean equality is not emitted
|
|
129
170
|
# (matches C++ behaviour — use integer 1/0 for boolean equality).
|
|
130
171
|
if action == "null"
|
|
172
|
+
|
|
131
173
|
action_str = params ? " IS " : " IS NOT "
|
|
174
|
+
|
|
132
175
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
176
|
+
|
|
133
177
|
@sql << Sanitizer.keyword_wrap(column)
|
|
178
|
+
|
|
134
179
|
@sql << action_str << "NULL"
|
|
135
180
|
end
|
|
136
181
|
|
|
137
182
|
when Integer
|
|
183
|
+
|
|
138
184
|
action_name = get_action(action)
|
|
185
|
+
|
|
139
186
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
187
|
+
|
|
140
188
|
@sql << Sanitizer.keyword_wrap(column)
|
|
189
|
+
|
|
141
190
|
@sql << " #{action_name} #{params}"
|
|
142
191
|
|
|
143
192
|
when Float
|
|
193
|
+
|
|
144
194
|
action_name = get_action(action)
|
|
195
|
+
|
|
145
196
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
197
|
+
|
|
146
198
|
@sql << Sanitizer.keyword_wrap(column)
|
|
199
|
+
|
|
147
200
|
@sql << " #{action_name} #{params}"
|
|
148
201
|
|
|
149
202
|
when String
|
|
203
|
+
|
|
150
204
|
build_action_string(params, column, action)
|
|
151
205
|
|
|
152
206
|
when Array
|
|
207
|
+
|
|
153
208
|
action_name = get_action(action)
|
|
209
|
+
|
|
154
210
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
211
|
+
|
|
155
212
|
@sql << Sanitizer.keyword_wrap(column)
|
|
213
|
+
|
|
156
214
|
@sql << " #{action_name} ("
|
|
215
|
+
|
|
157
216
|
build_array(params)
|
|
217
|
+
|
|
158
218
|
@sql << ")"
|
|
159
219
|
|
|
160
220
|
when Hash
|
|
221
|
+
|
|
161
222
|
action_name = get_action(action)
|
|
223
|
+
|
|
162
224
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
225
|
+
|
|
163
226
|
@sql << Sanitizer.keyword_wrap(column)
|
|
227
|
+
|
|
164
228
|
@sql << " #{action_name} ("
|
|
229
|
+
|
|
165
230
|
build_object(params)
|
|
231
|
+
|
|
166
232
|
@sql << ")"
|
|
167
233
|
end
|
|
168
234
|
end
|
|
169
235
|
|
|
170
236
|
def build_action_string(params, column, action)
|
|
237
|
+
|
|
171
238
|
action_name = get_action(action)
|
|
172
239
|
|
|
173
240
|
case action_name
|
|
174
241
|
when "last"
|
|
242
|
+
|
|
175
243
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
244
|
+
|
|
176
245
|
@sql << Sanitizer.keyword_wrap(column)
|
|
246
|
+
|
|
177
247
|
@sql << " LIKE '%" << Sanitizer.value(params) << "'"
|
|
178
248
|
|
|
179
249
|
when "first"
|
|
250
|
+
|
|
180
251
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
252
|
+
|
|
181
253
|
@sql << Sanitizer.keyword_wrap(column)
|
|
254
|
+
|
|
182
255
|
@sql << " LIKE '" << Sanitizer.value(params) << "%'"
|
|
183
256
|
|
|
184
257
|
when "contains"
|
|
258
|
+
|
|
185
259
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
260
|
+
|
|
186
261
|
@sql << Sanitizer.keyword_wrap(column)
|
|
262
|
+
|
|
187
263
|
@sql << " LIKE '%" << Sanitizer.value(params) << "%'"
|
|
188
264
|
|
|
189
265
|
else
|
|
266
|
+
|
|
190
267
|
if params.start_with?("$.")
|
|
268
|
+
|
|
191
269
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
270
|
+
|
|
192
271
|
@sql << Sanitizer.keyword_wrap(column)
|
|
272
|
+
|
|
193
273
|
@sql << " #{action_name} "
|
|
274
|
+
|
|
194
275
|
@sql << Sanitizer.reference(params)
|
|
276
|
+
|
|
195
277
|
else
|
|
278
|
+
|
|
196
279
|
@sql << Sanitizer.keyword_wrap(@table) << "."
|
|
280
|
+
|
|
197
281
|
@sql << Sanitizer.keyword_wrap(column)
|
|
282
|
+
|
|
198
283
|
@sql << " #{action_name} "
|
|
284
|
+
|
|
199
285
|
@sql << Sanitizer.value_wrap(params)
|
|
200
286
|
end
|
|
201
287
|
end
|
|
@@ -206,40 +292,57 @@ module Json2sql
|
|
|
206
292
|
# -------------------------------------------------------------------------
|
|
207
293
|
|
|
208
294
|
def build_array(array)
|
|
295
|
+
|
|
209
296
|
if array.empty?
|
|
297
|
+
|
|
210
298
|
@sql << "NULL"
|
|
299
|
+
|
|
211
300
|
return
|
|
212
301
|
end
|
|
213
302
|
|
|
214
303
|
glue = false
|
|
304
|
+
|
|
215
305
|
array.each do |item|
|
|
306
|
+
|
|
216
307
|
@sql << ", " if glue
|
|
308
|
+
|
|
217
309
|
glue = true
|
|
218
310
|
|
|
219
311
|
case item
|
|
220
|
-
when
|
|
221
|
-
when
|
|
312
|
+
when Float then @sql << item.to_s
|
|
313
|
+
when Integer then @sql << item.to_s
|
|
314
|
+
when String then @sql << Sanitizer.value_wrap(item)
|
|
222
315
|
end
|
|
223
316
|
end
|
|
224
317
|
end
|
|
225
318
|
|
|
226
319
|
# Builds a UNION of sub-SELECTs (used when action value is a Hash of tables).
|
|
320
|
+
|
|
227
321
|
def build_object(object)
|
|
322
|
+
|
|
228
323
|
if object.empty?
|
|
324
|
+
|
|
229
325
|
@sql << "NULL"
|
|
326
|
+
|
|
230
327
|
return
|
|
231
328
|
end
|
|
232
329
|
|
|
233
330
|
glue = false
|
|
331
|
+
|
|
234
332
|
relation = WhereRelation.none(@table)
|
|
235
333
|
|
|
236
334
|
object.each do |key, value|
|
|
335
|
+
|
|
237
336
|
@sql << " UNION " if glue
|
|
337
|
+
|
|
238
338
|
glue = true
|
|
239
339
|
|
|
240
340
|
tbl = key.to_s
|
|
341
|
+
|
|
241
342
|
@sql << "("
|
|
343
|
+
|
|
242
344
|
SelectModel.new(@sql, tbl, relation).build_query_default(value)
|
|
345
|
+
|
|
243
346
|
@sql << ")"
|
|
244
347
|
end
|
|
245
348
|
end
|
|
@@ -249,6 +352,7 @@ module Json2sql
|
|
|
249
352
|
# -------------------------------------------------------------------------
|
|
250
353
|
|
|
251
354
|
def get_action(action)
|
|
355
|
+
|
|
252
356
|
case action
|
|
253
357
|
when "=", "<", ">", "<=", ">=", "!=", "<>" then action
|
|
254
358
|
when "in" then "IN"
|
|
@@ -258,5 +362,7 @@ module Json2sql
|
|
|
258
362
|
else action
|
|
259
363
|
end
|
|
260
364
|
end
|
|
365
|
+
|
|
261
366
|
end
|
|
367
|
+
|
|
262
368
|
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.1
|
|
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
|