database-core 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +116 -0
- data/Rakefile +4 -0
- data/lib/database/v1/query.rb +144 -0
- data/lib/database/v1/query_runner.rb +157 -0
- data/lib/database/v2/query.rb +121 -0
- data/lib/database/v2/query_runner.rb +100 -0
- data/lib/database/v2/wherer.rb +62 -0
- data/lib/database/v3/delete_model.rb +18 -0
- data/lib/database/v3/delete_runner.rb +20 -0
- data/lib/database/v3/insert_model.rb +33 -0
- data/lib/database/v3/insert_runner.rb +20 -0
- data/lib/database/v3/policy_model.rb +29 -0
- data/lib/database/v3/policy_runner.rb +23 -0
- data/lib/database/v3/query_model.rb +126 -0
- data/lib/database/v3/query_runner.rb +163 -0
- data/lib/database/v3/sanitize.rb +19 -0
- data/lib/database/v3/update_model.rb +30 -0
- data/lib/database/v3/update_runner.rb +20 -0
- data/lib/database/v3/where_model.rb +144 -0
- data/lib/database/version.rb +5 -0
- data/lib/database.rb +7 -0
- data/sig/database.rbs +4 -0
- metadata +87 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Database::V2
|
|
2
|
+
|
|
3
|
+
class Query
|
|
4
|
+
|
|
5
|
+
def self.build table, query
|
|
6
|
+
|
|
7
|
+
output = []
|
|
8
|
+
|
|
9
|
+
build_select output, query
|
|
10
|
+
|
|
11
|
+
output << "FROM `#{table}`"
|
|
12
|
+
|
|
13
|
+
build_where output, query
|
|
14
|
+
build_order output, query
|
|
15
|
+
build_limit output, query
|
|
16
|
+
build_offset output, query
|
|
17
|
+
|
|
18
|
+
output.join " "
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.build_select output, query
|
|
22
|
+
|
|
23
|
+
columns = []
|
|
24
|
+
|
|
25
|
+
build_keys columns, query
|
|
26
|
+
|
|
27
|
+
unless query["columns"].nil?
|
|
28
|
+
columns.concat query["columns"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if columns.empty?
|
|
32
|
+
output << "SELECT *"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless columns.empty?
|
|
36
|
+
columns.map!{ |column| "`#{column}`" }
|
|
37
|
+
output << "SELECT #{columns.join ","}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.build_where output, query
|
|
42
|
+
|
|
43
|
+
where_rules = []
|
|
44
|
+
|
|
45
|
+
unless query["where_and"].nil?
|
|
46
|
+
where_rules << Wherer.build(query["where_and"]).join(" AND ")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless query["where_or"].nil?
|
|
50
|
+
where_rules << Wherer.build(query["where_and"]).join(" OR ")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
unless where_rules.empty?
|
|
54
|
+
output << "WHERE #{where_rules.join(" AND ")}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.build_order output, query
|
|
59
|
+
|
|
60
|
+
order = []
|
|
61
|
+
|
|
62
|
+
unless query["order"].nil?
|
|
63
|
+
query["order"].each do |key, value|
|
|
64
|
+
order << "`#{key}` #{value}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless order.empty?
|
|
69
|
+
order = order.join ","
|
|
70
|
+
output << "ORDER BY #{order}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.build_limit output, query
|
|
75
|
+
|
|
76
|
+
unless query["limit"].nil?
|
|
77
|
+
output << "LIMIT #{query["limit"]}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.build_offset output, query
|
|
82
|
+
|
|
83
|
+
unless query["offset"].nil?
|
|
84
|
+
output << "OFFSET #{query["offset"]}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.build_keys output, query
|
|
89
|
+
|
|
90
|
+
unless query["has_one_key"].nil?
|
|
91
|
+
unless output.include? "id"
|
|
92
|
+
output << "id"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unless query["has_many"].nil?
|
|
97
|
+
unless output.include? "id"
|
|
98
|
+
output << "id"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
unless query["has_many_key"].nil?
|
|
103
|
+
unless output.include? query["has_many_key"]
|
|
104
|
+
output << query["has_many_key"]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
unless query["has_one"].nil?
|
|
109
|
+
query["has_one"].each do |table, query|
|
|
110
|
+
unless query["has_one_key"].nil?
|
|
111
|
+
unless output.include? query["has_one_key"]
|
|
112
|
+
output << query["has_one_key"]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Database::V2
|
|
2
|
+
|
|
3
|
+
class QueryRunner
|
|
4
|
+
|
|
5
|
+
def self.query models_query
|
|
6
|
+
|
|
7
|
+
models_values = {}
|
|
8
|
+
|
|
9
|
+
models_query.each do |model, query|
|
|
10
|
+
|
|
11
|
+
sql = Query.build(model, query)
|
|
12
|
+
|
|
13
|
+
values = ActiveRecord::Base.connection.exec_query(sql).map(&:as_json)
|
|
14
|
+
|
|
15
|
+
if values.present?
|
|
16
|
+
|
|
17
|
+
setup_relation values, query["has_one"], true
|
|
18
|
+
setup_relation values, query["has_many"], false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
models_values[model] = values
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
serialize(models_values)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.setup_relation parents, models_query, relation
|
|
28
|
+
|
|
29
|
+
return unless models_query.present?
|
|
30
|
+
|
|
31
|
+
setup_relation_query(parents, models_query, relation)
|
|
32
|
+
setup_relation_value(parents, models_query, relation)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.setup_relation_query parents, models_query, relation
|
|
36
|
+
|
|
37
|
+
models_query.each do |model, query|
|
|
38
|
+
|
|
39
|
+
next if query["has_one_key"].nil? && relation == true
|
|
40
|
+
next if query["has_many_key"].nil? && relation == false
|
|
41
|
+
|
|
42
|
+
foreign_key = relation ? "id" : query["has_many_key"]
|
|
43
|
+
primary_key = relation ? query["has_one_key"] : "id"
|
|
44
|
+
|
|
45
|
+
keys = parents.map{ |parent| parent[primary_key] }.uniq.compact
|
|
46
|
+
|
|
47
|
+
next if keys.empty?
|
|
48
|
+
|
|
49
|
+
query["in_value"] = {} if query["in_value"].nil?
|
|
50
|
+
|
|
51
|
+
if query["in_value"][foreign_key]
|
|
52
|
+
query["in_value"][foreign_key] << keys
|
|
53
|
+
query["in_value"][foreign_key].flatten
|
|
54
|
+
else
|
|
55
|
+
query["in_value"][foreign_key] = keys
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.setup_relation_value parents, models_query, relation
|
|
61
|
+
|
|
62
|
+
models_values = query(models_query)
|
|
63
|
+
|
|
64
|
+
parents.each do |parent|
|
|
65
|
+
|
|
66
|
+
models_query.each do |model, query|
|
|
67
|
+
|
|
68
|
+
next if query["has_one_key"].nil? && relation == true
|
|
69
|
+
next if query["has_many_key"].nil? && relation == false
|
|
70
|
+
|
|
71
|
+
foreign_key = relation ? "id" : query["has_many_key"]
|
|
72
|
+
primary_key = relation ? query["has_one_key"] : "id"
|
|
73
|
+
|
|
74
|
+
values = models_values[model].select{ |value| value[foreign_key] == parent[primary_key] }
|
|
75
|
+
|
|
76
|
+
parent[model] = relation ? values.first : values
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.serialize(target)
|
|
82
|
+
|
|
83
|
+
case target
|
|
84
|
+
when Integer
|
|
85
|
+
target > 2**32 ? target.to_s : target
|
|
86
|
+
when String
|
|
87
|
+
encodings = [Encoding::US_ASCII, Encoding::UTF_8]
|
|
88
|
+
encodings.include?(target.encoding) ? target : target.bytes.to_a
|
|
89
|
+
when Array
|
|
90
|
+
target.map { |value| serialize(value) }
|
|
91
|
+
when Hash
|
|
92
|
+
target.transform_values { |value| serialize(value) }
|
|
93
|
+
else
|
|
94
|
+
target
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Database::V2
|
|
2
|
+
|
|
3
|
+
class Wherer
|
|
4
|
+
|
|
5
|
+
def self.build query
|
|
6
|
+
|
|
7
|
+
where_rules = []
|
|
8
|
+
|
|
9
|
+
unless query["bigger"].nil?
|
|
10
|
+
query["bigger"].each do |key, value|
|
|
11
|
+
where_rules << "`#{key}` = #{value}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
unless query["smaller"].nil?
|
|
16
|
+
query["smaller"].each do |key, value|
|
|
17
|
+
where_rules << "`#{key}` = #{value}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless query["different"].nil?
|
|
22
|
+
query["different"].each do |key, value|
|
|
23
|
+
where_rules << "`#{key}` = #{value}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
unless query["equal"].nil?
|
|
28
|
+
query["equal"].each do |key, value|
|
|
29
|
+
where_rules << "`#{key}` = #{value}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
unless query["in_query"].nil?
|
|
34
|
+
query["in_query"].each do |key, value|
|
|
35
|
+
value.each do |table, query|
|
|
36
|
+
query = Query.build table, query
|
|
37
|
+
where_rules << "`#{key}` IN (#{query})"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless query["in_value"].nil?
|
|
43
|
+
query["in_value"].each do |key, value|
|
|
44
|
+
unless value.empty?
|
|
45
|
+
value = value.join ","
|
|
46
|
+
where_rules << "`#{key}` IN (#{value})"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless query["like"].nil?
|
|
52
|
+
query["like"].each do |key, value|
|
|
53
|
+
where_rules << "`#{key}` LIKE '#{value}'"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
where_rules
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class DeleteRunner
|
|
4
|
+
|
|
5
|
+
def self.delete input
|
|
6
|
+
|
|
7
|
+
input.each do |model, payload|
|
|
8
|
+
|
|
9
|
+
Array.wrap(payload).each do |item|
|
|
10
|
+
|
|
11
|
+
sql = DeleteModel.build(model, item)
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.connection.execute(sql)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class InsertModel
|
|
4
|
+
|
|
5
|
+
def self.build model, params
|
|
6
|
+
|
|
7
|
+
model = Sanitize.target(model)
|
|
8
|
+
|
|
9
|
+
datetime = Time.current.utc.strftime("%Y-%m-%d %H:%M:%S")
|
|
10
|
+
|
|
11
|
+
params["columns"]["created_at"] = datetime
|
|
12
|
+
params["columns"]["updated_at"] = datetime
|
|
13
|
+
|
|
14
|
+
columns = build_columns(params).join(", ")
|
|
15
|
+
|
|
16
|
+
values = build_values(params).join(", ")
|
|
17
|
+
|
|
18
|
+
"INSERT INTO `#{model}` (#{columns}) VALUES (#{values})"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.build_columns params
|
|
22
|
+
|
|
23
|
+
params["columns"].keys.map{ |column| "`#{Sanitize.target(column)}`" }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.build_values params
|
|
27
|
+
|
|
28
|
+
params["columns"].values.map{ |value| Sanitize.array(["?", value]) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class InsertRunner
|
|
4
|
+
|
|
5
|
+
def self.insert input
|
|
6
|
+
|
|
7
|
+
input.each do |model, payload|
|
|
8
|
+
|
|
9
|
+
Array.wrap(payload).each do |item|
|
|
10
|
+
|
|
11
|
+
sql = InsertModel.build(model, item)
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.connection.execute(sql)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class PolicyModel
|
|
4
|
+
|
|
5
|
+
def initialize permissions, overrides
|
|
6
|
+
|
|
7
|
+
@permissions = permissions
|
|
8
|
+
|
|
9
|
+
@overrides = overrides
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def permit? model, params, permission
|
|
13
|
+
|
|
14
|
+
override = @overrides.dig(model)
|
|
15
|
+
|
|
16
|
+
return @permissions.dig(permission) unless override
|
|
17
|
+
|
|
18
|
+
params_columns = params.dig("columns").to_a
|
|
19
|
+
|
|
20
|
+
override_columns = override.dig("columns").to_a
|
|
21
|
+
|
|
22
|
+
return @permissions.dig(permission) unless (params_columns - override_columns).empty?
|
|
23
|
+
|
|
24
|
+
override.dig("permissions", permission)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class PolicyRunner
|
|
4
|
+
|
|
5
|
+
def initialize permissions, overrides
|
|
6
|
+
|
|
7
|
+
@policy_model = PolicyModel.new(permissions, overrides)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def permit? input, permission
|
|
11
|
+
|
|
12
|
+
input.all? do |model, payload|
|
|
13
|
+
|
|
14
|
+
Array.wrap(payload).all? do |item|
|
|
15
|
+
|
|
16
|
+
@policy_model.permit?(model, item, permission)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Database::V3
|
|
2
|
+
|
|
3
|
+
class QueryModel
|
|
4
|
+
|
|
5
|
+
def self.build model, query
|
|
6
|
+
|
|
7
|
+
model = Sanitize.target(model)
|
|
8
|
+
|
|
9
|
+
columns = build_columns(query)
|
|
10
|
+
|
|
11
|
+
output = ["SELECT #{columns} FROM `#{model}`"]
|
|
12
|
+
|
|
13
|
+
WhereModel.build(output, query)
|
|
14
|
+
|
|
15
|
+
build_order(output, query)
|
|
16
|
+
build_limit(output, query)
|
|
17
|
+
build_offset(output, query)
|
|
18
|
+
|
|
19
|
+
output.join(" ")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.build_count model, query
|
|
23
|
+
|
|
24
|
+
model = Sanitize.target(model)
|
|
25
|
+
|
|
26
|
+
output = ["SELECT COUNT(id) AS `#{model}` FROM `#{model}`"]
|
|
27
|
+
|
|
28
|
+
WhereModel.build(output, query)
|
|
29
|
+
|
|
30
|
+
output.join(" ")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.build_children model, query
|
|
34
|
+
|
|
35
|
+
children = query["children"]
|
|
36
|
+
|
|
37
|
+
return unless children.present?
|
|
38
|
+
|
|
39
|
+
children.each do |_model, value|
|
|
40
|
+
|
|
41
|
+
value["key"] = "#{model.singularize}_id" if value["key"].nil?
|
|
42
|
+
|
|
43
|
+
value["keys"] = [] if value["keys"].nil?
|
|
44
|
+
|
|
45
|
+
value["keys"] << value["key"] unless value["keys"].include? value["key"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
query["keys"] = [] if query["keys"].nil?
|
|
49
|
+
|
|
50
|
+
query["keys"] << "id" unless query["keys"].include? "id"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.build_parents _model, query
|
|
54
|
+
|
|
55
|
+
parents = query["parents"]
|
|
56
|
+
|
|
57
|
+
return unless parents.present?
|
|
58
|
+
|
|
59
|
+
parents.each do |model, value|
|
|
60
|
+
|
|
61
|
+
value["key"] = "#{model.singularize}_id" if value["key"].nil?
|
|
62
|
+
|
|
63
|
+
query["keys"] = [] if query["keys"].nil?
|
|
64
|
+
|
|
65
|
+
query["keys"] << value["key"] unless query["keys"].include? value["key"]
|
|
66
|
+
|
|
67
|
+
value["keys"] = [] if value["keys"].nil?
|
|
68
|
+
|
|
69
|
+
value["keys"] << "id" unless value["keys"].include? "id"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.build_columns query
|
|
74
|
+
|
|
75
|
+
columns = query["columns"].to_a + query["keys"].to_a
|
|
76
|
+
|
|
77
|
+
columns = columns.uniq.map{ |column| column.is_a?(Hash) ? build_function(column) : "`#{column}`" }.join(",")
|
|
78
|
+
|
|
79
|
+
columns.present? ? columns : "*"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.build_function function
|
|
83
|
+
|
|
84
|
+
function_name = function["function"].upcase
|
|
85
|
+
|
|
86
|
+
params = function["params"].map{ |param| param.is_a?(Hash) ? build_function(param) : "`#{param}`" }.join(", ")
|
|
87
|
+
|
|
88
|
+
function_call = "#{function_name}(#{params})"
|
|
89
|
+
|
|
90
|
+
function["alias"] ? "#{function_call} AS `#{function['alias']}`" : function_call
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.build_order output, query
|
|
94
|
+
|
|
95
|
+
order = query["order"]
|
|
96
|
+
|
|
97
|
+
return unless order.present?
|
|
98
|
+
|
|
99
|
+
order = order.map{ |key, value| "`#{key}` #{value}" }.join(",")
|
|
100
|
+
|
|
101
|
+
return unless order.present?
|
|
102
|
+
|
|
103
|
+
output << "ORDER BY #{order}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.build_limit output, query
|
|
107
|
+
|
|
108
|
+
limit = query["limit"].to_i
|
|
109
|
+
|
|
110
|
+
return if limit.zero?
|
|
111
|
+
|
|
112
|
+
output << "LIMIT #{limit}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.build_offset output, query
|
|
116
|
+
|
|
117
|
+
offset = query["offset"].to_i
|
|
118
|
+
|
|
119
|
+
return if offset.zero?
|
|
120
|
+
|
|
121
|
+
output << "OFFSET #{offset}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|