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.
@@ -0,0 +1,163 @@
1
+ module Database::V3
2
+
3
+ class QueryRunner
4
+
5
+ def self.query models_query
6
+
7
+ models_values = setup_query(models_query)
8
+
9
+ remove_keys(models_query, models_values)
10
+
11
+ models_values = setup_values(models_values)
12
+
13
+ models_values
14
+ end
15
+
16
+ def self.setup_query models_query
17
+
18
+ models_values = { "rows" => {} }
19
+
20
+ models_query.each do |model, query|
21
+
22
+ QueryModel.build_parents(model, query)
23
+ QueryModel.build_children(model, query)
24
+
25
+ sql = QueryModel.build(model, query)
26
+
27
+ values = ActiveRecord::Base.connection.exec_query(sql).map(&:as_json)
28
+
29
+ if query["rows"]
30
+
31
+ sql = QueryModel.build_count(model, query)
32
+
33
+ rows = ActiveRecord::Base.connection.exec_query(sql).first.as_json
34
+
35
+ models_values["rows"].merge!(rows)
36
+ end
37
+
38
+ if values.present?
39
+
40
+ setup_relation values, query["parents"], true
41
+ setup_relation values, query["children"], false
42
+ end
43
+
44
+ models_values[model] = values
45
+ end
46
+
47
+ models_values
48
+ end
49
+
50
+ def self.setup_relation parents, models_query, relation
51
+
52
+ return unless models_query.present?
53
+
54
+ setup_relation_query(parents, models_query, relation)
55
+ setup_relation_value(parents, models_query, relation)
56
+ end
57
+
58
+ def self.setup_relation_query parents, models_query, relation
59
+
60
+ models_query.each do |model, query|
61
+
62
+ next if query["key"].nil?
63
+
64
+ foreign_key = relation ? "id" : query["key"]
65
+ primary_key = relation ? query["key"] : "id"
66
+
67
+ keys = parents.map{ |parent| parent[primary_key] }.uniq.compact
68
+
69
+ next if keys.empty?
70
+
71
+ query["and"] = {} if query["and"].nil?
72
+
73
+ if query["and"][foreign_key]
74
+ query["and"][foreign_key]["in"] ?
75
+ query["and"][foreign_key]["in"] << keys :
76
+ query["and"][foreign_key]["in"] = keys
77
+ else
78
+ query["and"][foreign_key] = { "in" => keys }
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.setup_relation_value parents, models_query, relation
84
+
85
+ models_values = setup_query(models_query)
86
+
87
+ parents.each do |parent|
88
+
89
+ models_query.each do |model, query|
90
+
91
+ next if query["key"].nil?
92
+
93
+ foreign_key = relation ? "id" : query["key"]
94
+ primary_key = relation ? query["key"] : "id"
95
+
96
+ values = models_values[model].select{ |value| value[foreign_key] == parent[primary_key] }
97
+
98
+ parent[model] = relation ? values.first : values
99
+ end
100
+ end
101
+ end
102
+
103
+ def self.setup_values target
104
+
105
+ if target.is_a?(Integer)
106
+ return target > 2**32 ? target.to_s : target
107
+ end
108
+
109
+ if target.is_a?(Array)
110
+ return target.map { |value| setup_values(value) }
111
+ end
112
+
113
+ if target.is_a?(Hash)
114
+ return target.transform_values { |value| setup_values(value) }
115
+ end
116
+
117
+ if target.is_a?(String)
118
+ unless [Encoding::US_ASCII, Encoding::UTF_8].include?(target.encoding)
119
+ return target.bytes.to_a
120
+ end
121
+ end
122
+
123
+ target
124
+ end
125
+
126
+ def self.remove_keys models_query, models_values
127
+
128
+ models_query&.each do |model, query|
129
+
130
+ if models_values[model].is_a?(Array)
131
+ models_values[model].each do |value|
132
+ remove_key query, value
133
+ end
134
+ end
135
+
136
+ if models_values[model].is_a?(Hash)
137
+ value = models_values[model]
138
+ remove_key query, value
139
+ end
140
+ end
141
+ end
142
+
143
+ def self.remove_key query, value
144
+
145
+ columns = query["columns"]&.map{ |column| column.is_a?(Hash) ? column["alias"] : column }
146
+
147
+ value.keys.each do |key|
148
+
149
+ next if columns.include?(key)
150
+
151
+ next if query["parents"]&.key?(key)
152
+ next if query["children"]&.key?(key)
153
+
154
+ value.delete(key)
155
+ end
156
+
157
+ remove_keys query["parents"], value
158
+ remove_keys query["children"], value
159
+ end
160
+
161
+ end
162
+
163
+ end
@@ -0,0 +1,19 @@
1
+ module Database::V3
2
+
3
+ RULE = /[^a-zA-Z0-9_]/.freeze
4
+
5
+ class Sanitize
6
+
7
+ def self.target params
8
+
9
+ params.to_s.gsub(RULE, "")
10
+ end
11
+
12
+ def self.array params
13
+
14
+ ActiveRecord::Base.sanitize_sql_array(params)
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,30 @@
1
+ module Database::V3
2
+
3
+ class UpdateModel
4
+
5
+ def self.build model, params
6
+
7
+ model = Sanitize.target(model)
8
+
9
+ columns = build_columns(params).join(", ")
10
+
11
+ output = ["UPDATE `#{model}` SET #{columns}"]
12
+
13
+ WhereModel.build(output, params)
14
+
15
+ output.join(" ")
16
+ end
17
+
18
+ def self.build_columns params
19
+
20
+ params["columns"].map do |column, value|
21
+
22
+ column = Sanitize.target(column)
23
+
24
+ Sanitize.array(["`#{column}` = ?", value])
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,20 @@
1
+ module Database::V3
2
+
3
+ class UpdateRunner
4
+
5
+ def self.update input
6
+
7
+ input.each do |model, payload|
8
+
9
+ Array.wrap(payload).each do |item|
10
+
11
+ sql = UpdateModel.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,144 @@
1
+ module Database::V3
2
+
3
+ class WhereModel
4
+
5
+ OPERATORS = {
6
+
7
+ "=" => "=",
8
+ "!=" => "!=",
9
+ "<>" => "<>",
10
+ ">" => ">",
11
+ "<" => "<",
12
+ ">=" => ">=",
13
+ "<=" => "<=",
14
+ "in" => "IN",
15
+ "like" => "LIKE",
16
+ "!in" => "NOT IN",
17
+ "!like" => "NOT LIKE",
18
+ }
19
+
20
+ def self.build output, query
21
+
22
+ where = []
23
+
24
+ where << query["and"].to_h.map{ |key, value| build_scopes(" AND ", key, value) }.compact_blank.join(" AND ")
25
+
26
+ where << query["or"].to_h.map{ |key, value| build_scopes(" OR ", key, value) }.compact_blank.join(" OR ")
27
+
28
+ where = where.compact_blank.join(" AND ")
29
+
30
+ return unless where.present?
31
+
32
+ output << "WHERE #{where}"
33
+ end
34
+
35
+ def self.build_scopes scope, column, rules
36
+
37
+ if rules.is_a? FalseClass
38
+
39
+ return build_rules(column, "=", rules)
40
+ end
41
+
42
+ if rules.is_a? TrueClass
43
+
44
+ return build_rules(column, "=", rules)
45
+ end
46
+
47
+ if rules.is_a? Numeric
48
+
49
+ return build_rules(column, "=", rules)
50
+ end
51
+
52
+ if rules.is_a? String
53
+
54
+ return build_rules(column, "contains", rules)
55
+ end
56
+
57
+ output = build_scope(scope, column, rules)
58
+
59
+ output.present? ? "(#{output})" : ""
60
+ end
61
+
62
+ def self.build_scope scope, column, rules
63
+
64
+ case column
65
+ when "and"
66
+ return rules.map{ |key, value| build_scopes(" AND ", key, value) }.compact_blank.join(" AND ")
67
+ when "or"
68
+ return rules.map{ |key, value| build_scopes(" OR ", key, value) }.compact_blank.join(" OR ")
69
+ else
70
+ return rules.map{ |key, value| build_rules(column, key, value) }.compact_blank.join(scope)
71
+ end
72
+ end
73
+
74
+ def self.build_rules column, operator, value
75
+
76
+ case operator
77
+ when "and"
78
+ return build_scopes(" AND ", column, value)
79
+ when "or"
80
+ return build_scopes(" OR ", column, value)
81
+ else
82
+ return build_rule(column, operator, value)
83
+ end
84
+ end
85
+
86
+ def self.build_rule column, operator, value
87
+
88
+ return "" if value.is_a? NilClass
89
+
90
+ column = Sanitize.target(column)
91
+
92
+ if operator == "null"
93
+
94
+ value = value ? " " : " NOT "
95
+
96
+ return "`#{column}` IS#{value}NULL"
97
+ end
98
+
99
+ if operator == "last"
100
+ return Sanitize.array(["`#{column}` LIKE ?", "%#{value}"])
101
+ end
102
+
103
+ if operator == "first"
104
+ return Sanitize.array(["`#{column}` LIKE ?", "#{value}%"])
105
+ end
106
+
107
+ if operator == "contains"
108
+ return Sanitize.array(["`#{column}` LIKE ?", "%#{value}%"])
109
+ end
110
+
111
+ operator = get_operator(operator)
112
+
113
+ if value.is_a? Numeric
114
+ return Sanitize.array(["`#{column}` #{operator} ?", value])
115
+ end
116
+
117
+ if value.is_a? String
118
+ return Sanitize.array(["`#{column}` #{operator} ?", value])
119
+ end
120
+
121
+ if value.is_a? Array
122
+ return Sanitize.array(["`#{column}` #{operator} (?)", value])
123
+ end
124
+
125
+ if value.is_a? Hash
126
+
127
+ value = value.map{ |model, query| QueryModel.build(model, query) }
128
+
129
+ value = value.map{ |value| "(#{value})" }.join(" UNION ")
130
+ end
131
+
132
+ "`#{column}` #{operator} #{value}"
133
+ end
134
+
135
+ def self.get_operator operator
136
+
137
+ return OPERATORS[operator] if OPERATORS.key?(operator)
138
+
139
+ raise KeyError, operator
140
+ end
141
+
142
+ end
143
+
144
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Database
4
+ VERSION = "0.1.0"
5
+ end
data/lib/database.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "database/version"
4
+
5
+ Dir[File.join(__dir__, "database/**/*.rb")].sort.each do |file|
6
+ require file
7
+ end
data/sig/database.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Database
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: database-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tiago da Silva
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ description: A comprehensive Ruby library providing database abstraction and management
28
+ features, making it easier to interact with various databases.
29
+ email:
30
+ - tyagoy@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.md
36
+ - CODE_OF_CONDUCT.md
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - lib/database.rb
41
+ - lib/database/v1/query.rb
42
+ - lib/database/v1/query_runner.rb
43
+ - lib/database/v2/query.rb
44
+ - lib/database/v2/query_runner.rb
45
+ - lib/database/v2/wherer.rb
46
+ - lib/database/v3/delete_model.rb
47
+ - lib/database/v3/delete_runner.rb
48
+ - lib/database/v3/insert_model.rb
49
+ - lib/database/v3/insert_runner.rb
50
+ - lib/database/v3/policy_model.rb
51
+ - lib/database/v3/policy_runner.rb
52
+ - lib/database/v3/query_model.rb
53
+ - lib/database/v3/query_runner.rb
54
+ - lib/database/v3/sanitize.rb
55
+ - lib/database/v3/update_model.rb
56
+ - lib/database/v3/update_runner.rb
57
+ - lib/database/v3/where_model.rb
58
+ - lib/database/version.rb
59
+ - sig/database.rbs
60
+ homepage: https://rubygems.org/gems/database
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ allowed_push_host: https://rubygems.org
65
+ homepage_uri: https://rubygems.org/gems/database
66
+ source_code_uri: https://github.com/tyagoy/database-core
67
+ changelog_uri: https://github.com/tyagoy/database-core/blob/main/CHANGELOG.md
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 3.2.0
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.4.10
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: A simple Ruby library for database abstraction and management.
87
+ test_files: []