-
Notifications
You must be signed in to change notification settings - Fork 53
/
model_query.cr
170 lines (152 loc) · 4.67 KB
/
model_query.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
require "./*"
module Jennifer
module QueryBuilder
class ModelQuery(T) < Query
def model_class
T
end
def table
@table.empty? ? T.table_name : @table
end
def with(*arr)
arr.map(&.to_s).to_a.each do |name|
table_name = T.relation(name).table_name
temp_joins = @joins.select { |j| j.table == table_name }
join = temp_joins.find(&.relation.nil?)
if join
join.not_nil!.relation = name
elsif temp_joins.size == 0
raise BaseException.new("#with should be called after correspond join: no such table \"#{table_name}\" of relation \"#{name}\"")
end
@relations << name
end
self
end
def relation(name, type = :inner)
T.relation(name.to_s).join_condition(self, type)
end
def includes(*names)
names.each do |name|
includes(name)
end
self
end
def includes(name : String | Symbol)
@relations << name.to_s
relation(name)
end
def includes(rels : Array(String), aliases = [] of String?)
@relations << name.to_s
raise "Not implemented"
end
def destroy
to_a.each(&.destroy)
end
# TODO: debug case when exception was rised under #each
def to_a
add_aliases if @relation_used
return to_a_with_relations if @relations.size > 0
result = [] of T
::Jennifer::Adapter.adapter.select(self) do |rs|
rs.each do
result << T.build(rs)
end
end
result
end
def select_clause(exact_fields = [] of String)
String.build do |s|
s << "SELECT "
unless @raw_select
if exact_fields.size > 0
exact_fields.map { |f| "#{table}.#{f}" }.join(", ", s)
else
s << table << ".*"
unless @relations.empty?
s << ", "
@relations.each_with_index do |r, i|
s << ", " if i != 0
s << (@table_aliases[r]? || model_class.relations[r].table_name) << ".*"
end
end
end
else
s << @raw_select
end
s << "\n"
from_clause(s)
end
end
# ========= private ==============
private def reverse_order
if @order.empty?
@order[T.primary_field_name] = "DESC"
else
super
end
end
private def to_a_with_relations
h_result = {} of String => T
models = @relations.map { |e| T.relations[e].model_class }
existence = @relations.map { |_| {} of String => Bool }
::Jennifer::Adapter.adapter.select(self) do |rs|
rs.each do
h = build_hash(rs, T.field_count)
main_field = T.primary_field_name
if h[main_field]?
obj = (h_result[h[main_field].to_s] ||= T.build(h, false))
models.each_with_index do |model, i|
h = build_hash(rs, model.field_count)
pfn = model.primary_field_name
if h[pfn].nil? || existence[i][h[pfn].to_s]?
(rs.column_count - rs.column_index).times do |i|
rs.read
end
break
else
existence[i][h[pfn].to_s] = true
obj.append_relation(@relations[i], h)
end
end
else
(rs.column_count - T.field_count).times { |_| rs.read }
end
end
end
h_result.values
end
private def add_aliases
table_names = [table] + @joins.map { |e| e.table if !e.aliass }.compact
duplicates = extract_duplicates(table_names)
return if duplicates.empty?
i = 0
@table_aliases.clear
@joins.each do |j|
if j.relation && duplicates.includes?(j.table)
@table_aliases[j.relation.as(String)] = "t#{i}"
i += 1
end
end
@joins.each { |j| j.alias_tables(@table_aliases) }
@tree.not_nil!.alias_tables(@table_aliases) if @tree
end
private def build_hash(rs, size)
h = {} of String => DBAny
size.times do |i|
h[rs.current_column_name] = rs.read(DBAny)
end
h
end
private def extract_duplicates(arr)
result = [] of String
entries = Hash(String, Int32).new(0)
arr.each do |name|
entries[name] += 1
end
result = [] of String
entries.each { |k, v| result << k if v > 1 }
result
end
end
end
end