pghero 1.4.2 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +8 -8
- data/app/assets/javascripts/pghero/Chart.bundle.js +183 -55
- data/app/assets/javascripts/pghero/chartkick.js +53 -20
- data/app/assets/stylesheets/pghero/application.css +7 -0
- data/app/controllers/pg_hero/home_controller.rb +61 -57
- data/app/views/layouts/pg_hero/application.html.erb +3 -3
- data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
- data/app/views/pg_hero/home/_queries_table.html.erb +6 -1
- data/app/views/pg_hero/home/connections.html.erb +1 -1
- data/app/views/pg_hero/home/explain.html.erb +11 -2
- data/app/views/pg_hero/home/index.html.erb +4 -4
- data/app/views/pg_hero/home/maintenance.html.erb +2 -2
- data/app/views/pg_hero/home/system.html.erb +2 -2
- data/guides/Rails.md +8 -0
- data/lib/generators/pghero/space_stats_generator.rb +29 -0
- data/lib/generators/pghero/templates/space_stats.rb +13 -0
- data/lib/pghero.rb +109 -23
- data/lib/pghero/database.rb +46 -8
- data/lib/pghero/engine.rb +3 -1
- data/lib/pghero/methods/basic.rb +3 -42
- data/lib/pghero/methods/connections.rb +18 -1
- data/lib/pghero/methods/explain.rb +2 -0
- data/lib/pghero/methods/indexes.rb +2 -2
- data/lib/pghero/methods/kill.rb +1 -1
- data/lib/pghero/methods/queries.rb +2 -2
- data/lib/pghero/methods/query_stats.rb +81 -75
- data/lib/pghero/methods/space.rb +12 -1
- data/lib/pghero/methods/suggested_indexes.rb +71 -31
- data/lib/pghero/version.rb +1 -1
- data/lib/tasks/pghero.rake +5 -0
- metadata +4 -3
- data/lib/pghero/methods/databases.rb +0 -39
@@ -6,7 +6,24 @@ module PgHero
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def connection_sources(options = {})
|
9
|
-
if options[:
|
9
|
+
if options[:by_database_and_user]
|
10
|
+
select_all <<-SQL
|
11
|
+
SELECT
|
12
|
+
datname AS database,
|
13
|
+
usename AS user,
|
14
|
+
application_name AS source,
|
15
|
+
client_addr AS ip,
|
16
|
+
COUNT(*) AS total_connections
|
17
|
+
FROM
|
18
|
+
pg_stat_activity
|
19
|
+
WHERE
|
20
|
+
pid <> pg_backend_pid()
|
21
|
+
GROUP BY
|
22
|
+
1, 2, 3, 4
|
23
|
+
ORDER BY
|
24
|
+
5 DESC, 1, 2, 3, 4
|
25
|
+
SQL
|
26
|
+
elsif options[:by_database]
|
10
27
|
select_all <<-SQL
|
11
28
|
SELECT
|
12
29
|
application_name AS source,
|
@@ -140,8 +140,8 @@ module PgHero
|
|
140
140
|
indexes = []
|
141
141
|
|
142
142
|
indexes_by_table = self.indexes.group_by { |i| i["table"] }
|
143
|
-
indexes_by_table.values.flatten.select { |i| falsey?(i["primary"]) && falsey?(i["unique"]) && !i["indexprs"] && !i["indpred"] && truthy?(i["valid"]) }.each do |index|
|
144
|
-
covering_index = indexes_by_table[index["table"]].find { |i| index_covers?(i["columns"], index["columns"]) && i["using"] == index["using"] && i["name"] != index["name"] && truthy?(i["valid"]) }
|
143
|
+
indexes_by_table.values.flatten.select { |i| PgHero.falsey?(i["primary"]) && PgHero.falsey?(i["unique"]) && !i["indexprs"] && !i["indpred"] && PgHero.truthy?(i["valid"]) }.each do |index|
|
144
|
+
covering_index = indexes_by_table[index["table"]].find { |i| index_covers?(i["columns"], index["columns"]) && i["using"] == index["using"] && i["name"] != index["name"] && PgHero.truthy?(i["valid"]) }
|
145
145
|
if covering_index
|
146
146
|
indexes << {"unneeded_index" => index, "covering_index" => covering_index}
|
147
147
|
end
|
data/lib/pghero/methods/kill.rb
CHANGED
@@ -2,7 +2,7 @@ module PgHero
|
|
2
2
|
module Methods
|
3
3
|
module Kill
|
4
4
|
def kill(pid)
|
5
|
-
truthy? execute("SELECT pg_terminate_backend(#{pid.to_i})").first["pg_terminate_backend"]
|
5
|
+
PgHero.truthy? execute("SELECT pg_terminate_backend(#{pid.to_i})").first["pg_terminate_backend"]
|
6
6
|
end
|
7
7
|
|
8
8
|
def kill_long_running_queries
|
@@ -41,7 +41,7 @@ module PgHero
|
|
41
41
|
query <> '<insufficient privilege>'
|
42
42
|
AND state <> 'idle'
|
43
43
|
AND pid <> pg_backend_pid()
|
44
|
-
AND now() - query_start > interval '#{long_running_query_sec.to_i} seconds'
|
44
|
+
AND now() - query_start > interval '#{PgHero.long_running_query_sec.to_i} seconds'
|
45
45
|
AND datname = current_database()
|
46
46
|
ORDER BY
|
47
47
|
query_start DESC
|
@@ -50,7 +50,7 @@ module PgHero
|
|
50
50
|
|
51
51
|
def slow_queries(options = {})
|
52
52
|
query_stats = options[:query_stats] || self.query_stats(options.except(:query_stats))
|
53
|
-
query_stats.select { |q| q["calls"].to_i >= slow_query_calls.to_i && q["average_time"].to_i >= slow_query_ms.to_i }
|
53
|
+
query_stats.select { |q| q["calls"].to_i >= PgHero.slow_query_calls.to_i && q["average_time"].to_i >= PgHero.slow_query_ms.to_i }
|
54
54
|
end
|
55
55
|
|
56
56
|
def locks
|
@@ -5,8 +5,8 @@ module PgHero
|
|
5
5
|
current_query_stats = options[:historical] && options[:end_at] && options[:end_at] < Time.now ? [] : current_query_stats(options)
|
6
6
|
historical_query_stats = options[:historical] ? historical_query_stats(options) : []
|
7
7
|
|
8
|
-
query_stats = combine_query_stats((current_query_stats + historical_query_stats).group_by { |q| q["query_hash"] })
|
9
|
-
query_stats = combine_query_stats(query_stats.group_by { |q| normalize_query(q["query"]) })
|
8
|
+
query_stats = combine_query_stats((current_query_stats + historical_query_stats).group_by { |q| [q["query_hash"], q["user"]] })
|
9
|
+
query_stats = combine_query_stats(query_stats.group_by { |q| [normalize_query(q["query"]), q["user"]] })
|
10
10
|
|
11
11
|
# add percentages
|
12
12
|
all_queries_total_minutes = [current_query_stats, historical_query_stats].sum { |s| (s.first || {})["all_queries_total_minutes"].to_f }
|
@@ -63,65 +63,10 @@ module PgHero
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
# resetting query stats will reset across the entire Postgres instance
|
67
|
-
# this is problematic if multiple PgHero databases use the same Postgres instance
|
68
|
-
#
|
69
|
-
# to get around this, we capture queries for every Postgres database before we
|
70
|
-
# reset query stats for the Postgres instance with the `capture_query_stats` option
|
71
|
-
def capture_query_stats
|
72
|
-
# get database names
|
73
|
-
pg_databases = {}
|
74
|
-
supports_query_hash = {}
|
75
|
-
config["databases"].each do |k, _|
|
76
|
-
with(k) do
|
77
|
-
pg_databases[k] = execute("SELECT current_database()").first["current_database"]
|
78
|
-
supports_query_hash[k] = supports_query_hash?
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
config["databases"].reject { |_, v| v["capture_query_stats"] && v["capture_query_stats"] != true }.each do |database, _|
|
83
|
-
with(database) do
|
84
|
-
mapping = {database => pg_databases[database]}
|
85
|
-
config["databases"].select { |_, v| v["capture_query_stats"] == database }.each do |k, _|
|
86
|
-
mapping[k] = pg_databases[k]
|
87
|
-
end
|
88
|
-
|
89
|
-
now = Time.now
|
90
|
-
query_stats = {}
|
91
|
-
mapping.each do |db, pg_database|
|
92
|
-
query_stats[db] = self.query_stats(limit: 1000000, database: pg_database)
|
93
|
-
end
|
94
|
-
|
95
|
-
if query_stats.any? { |_, v| v.any? } && reset_query_stats
|
96
|
-
query_stats.each do |db, db_query_stats|
|
97
|
-
if db_query_stats.any?
|
98
|
-
values =
|
99
|
-
db_query_stats.map do |qs|
|
100
|
-
values = [
|
101
|
-
db,
|
102
|
-
qs["query"],
|
103
|
-
qs["total_minutes"].to_f * 60 * 1000,
|
104
|
-
qs["calls"],
|
105
|
-
now
|
106
|
-
]
|
107
|
-
values << qs["query_hash"] if supports_query_hash[db]
|
108
|
-
values.map { |v| quote(v) }.join(",")
|
109
|
-
end.map { |v| "(#{v})" }.join(",")
|
110
|
-
|
111
|
-
columns = %w[database query total_time calls captured_at]
|
112
|
-
columns << "query_hash" if supports_query_hash[db]
|
113
|
-
stats_connection.execute("INSERT INTO pghero_query_stats (#{columns.join(", ")}) VALUES #{values}")
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
66
|
# http://stackoverflow.com/questions/20582500/how-to-check-if-a-table-exists-in-a-given-schema
|
122
67
|
def historical_query_stats_enabled?
|
123
68
|
# TODO use schema from config
|
124
|
-
truthy?
|
69
|
+
PgHero.truthy?(stats_connection.select_all(squish <<-SQL
|
125
70
|
SELECT EXISTS (
|
126
71
|
SELECT
|
127
72
|
1
|
@@ -135,15 +80,80 @@ module PgHero
|
|
135
80
|
AND c.relkind = 'r'
|
136
81
|
)
|
137
82
|
SQL
|
138
|
-
).to_a.first["exists"]
|
83
|
+
).to_a.first["exists"]) && capture_query_stats?
|
139
84
|
end
|
140
85
|
|
141
|
-
def
|
142
|
-
|
86
|
+
def supports_query_hash?
|
87
|
+
@supports_query_hash ||= server_version >= 90400 && historical_query_stats_enabled? && PgHero::QueryStats.column_names.include?("query_hash")
|
88
|
+
end
|
89
|
+
|
90
|
+
def supports_query_stats_user?
|
91
|
+
@supports_query_stats_user ||= historical_query_stats_enabled? && PgHero::QueryStats.column_names.include?("user")
|
92
|
+
end
|
93
|
+
|
94
|
+
def insert_stats(table, columns, values)
|
95
|
+
values = values.map { |v| "(#{v.map { |v2| quote(v2) }.join(",")})" }.join(",")
|
96
|
+
columns = columns.map { |v| quote_table_name(v) }.join(",")
|
97
|
+
stats_connection.execute("INSERT INTO #{quote_table_name(table)} (#{columns}) VALUES #{values}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# resetting query stats will reset across the entire Postgres instance
|
101
|
+
# this is problematic if multiple PgHero databases use the same Postgres instance
|
102
|
+
#
|
103
|
+
# to get around this, we capture queries for every Postgres database before we
|
104
|
+
# reset query stats for the Postgres instance with the `capture_query_stats` option
|
105
|
+
def capture_query_stats
|
106
|
+
return if config["capture_query_stats"] && config["capture_query_stats"] != true
|
107
|
+
|
108
|
+
# get all databases that use same query stats and build mapping
|
109
|
+
mapping = {id => database_name}
|
110
|
+
PgHero.databases.select { |_, d| d.config["capture_query_stats"] == id }.each do |_, d|
|
111
|
+
mapping[d.id] = d.database_name
|
112
|
+
end
|
113
|
+
|
114
|
+
now = Time.now
|
115
|
+
|
116
|
+
query_stats = {}
|
117
|
+
mapping.each do |database_id, database_name|
|
118
|
+
query_stats[database_id] = query_stats(limit: 1000000, database: database_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
if query_stats.any? { |_, v| v.any? } && reset_query_stats
|
122
|
+
query_stats.each do |db_id, db_query_stats|
|
123
|
+
if db_query_stats.any?
|
124
|
+
supports_query_hash = PgHero.databases[db_id].supports_query_hash?
|
125
|
+
supports_query_stats_user = PgHero.databases[db_id].supports_query_stats_user?
|
126
|
+
|
127
|
+
values =
|
128
|
+
db_query_stats.map do |qs|
|
129
|
+
values = [
|
130
|
+
db_id,
|
131
|
+
qs["query"],
|
132
|
+
qs["total_minutes"].to_f * 60 * 1000,
|
133
|
+
qs["calls"],
|
134
|
+
now
|
135
|
+
]
|
136
|
+
values << qs["query_hash"] if supports_query_hash
|
137
|
+
values << qs["user"] if supports_query_stats_user
|
138
|
+
values
|
139
|
+
end
|
140
|
+
|
141
|
+
columns = %w[database query total_time calls captured_at]
|
142
|
+
columns << "query_hash" if supports_query_hash
|
143
|
+
columns << "user" if supports_query_stats_user
|
144
|
+
|
145
|
+
insert_stats("pghero_query_stats", columns, values)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
143
149
|
end
|
144
150
|
|
145
151
|
private
|
146
152
|
|
153
|
+
def stats_connection
|
154
|
+
::PgHero::QueryStats.connection
|
155
|
+
end
|
156
|
+
|
147
157
|
# http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/
|
148
158
|
def current_query_stats(options = {})
|
149
159
|
if query_stats_enabled?
|
@@ -155,6 +165,7 @@ module PgHero
|
|
155
165
|
SELECT
|
156
166
|
LEFT(query, 10000) AS query,
|
157
167
|
#{supports_query_hash? ? "queryid" : "md5(query)"} AS query_hash,
|
168
|
+
#{supports_query_stats_user? ? "rolname" : "NULL"} AS user,
|
158
169
|
(total_time / 1000 / 60) AS total_minutes,
|
159
170
|
(total_time / calls) AS average_time,
|
160
171
|
calls
|
@@ -162,12 +173,15 @@ module PgHero
|
|
162
173
|
pg_stat_statements
|
163
174
|
INNER JOIN
|
164
175
|
pg_database ON pg_database.oid = pg_stat_statements.dbid
|
176
|
+
INNER JOIN
|
177
|
+
pg_roles ON pg_roles.oid = pg_stat_statements.userid
|
165
178
|
WHERE
|
166
179
|
pg_database.datname = #{database}
|
167
180
|
)
|
168
181
|
SELECT
|
169
182
|
query,
|
170
183
|
query_hash,
|
184
|
+
query_stats.user,
|
171
185
|
total_minutes,
|
172
186
|
average_time,
|
173
187
|
calls,
|
@@ -191,6 +205,7 @@ module PgHero
|
|
191
205
|
WITH query_stats AS (
|
192
206
|
SELECT
|
193
207
|
#{supports_query_hash? ? "query_hash" : "md5(query)"} AS query_hash,
|
208
|
+
#{supports_query_stats_user? ? "pghero_query_stats.user" : "NULL"} AS user,
|
194
209
|
array_agg(LEFT(query, 10000)) AS query,
|
195
210
|
(SUM(total_time) / 1000 / 60) AS total_minutes,
|
196
211
|
(SUM(total_time) / SUM(calls)) AS average_time,
|
@@ -198,15 +213,16 @@ module PgHero
|
|
198
213
|
FROM
|
199
214
|
pghero_query_stats
|
200
215
|
WHERE
|
201
|
-
database = #{quote(
|
216
|
+
database = #{quote(id)}
|
202
217
|
#{supports_query_hash? ? "AND query_hash IS NOT NULL" : ""}
|
203
218
|
#{options[:start_at] ? "AND captured_at >= #{quote(options[:start_at])}" : ""}
|
204
219
|
#{options[:end_at] ? "AND captured_at <= #{quote(options[:end_at])}" : ""}
|
205
220
|
GROUP BY
|
206
|
-
1
|
221
|
+
1, 2
|
207
222
|
)
|
208
223
|
SELECT
|
209
224
|
query_hash,
|
225
|
+
query_stats.user,
|
210
226
|
query[1],
|
211
227
|
total_minutes,
|
212
228
|
average_time,
|
@@ -224,26 +240,16 @@ module PgHero
|
|
224
240
|
end
|
225
241
|
end
|
226
242
|
|
227
|
-
def supports_query_hash?
|
228
|
-
@supports_query_hash ||= {}
|
229
|
-
if @supports_query_hash[current_database].nil?
|
230
|
-
@supports_query_hash[current_database] = server_version >= 90400 && historical_query_stats_enabled? && PgHero::QueryStats.column_names.include?("query_hash")
|
231
|
-
end
|
232
|
-
@supports_query_hash[current_database]
|
233
|
-
end
|
234
|
-
|
235
243
|
def server_version
|
236
|
-
@server_version ||=
|
237
|
-
@server_version[current_database] ||= select_all("SHOW server_version_num").first["server_version_num"].to_i
|
244
|
+
@server_version ||= select_all("SHOW server_version_num").first["server_version_num"].to_i
|
238
245
|
end
|
239
246
|
|
240
|
-
private
|
241
|
-
|
242
247
|
def combine_query_stats(grouped_stats)
|
243
248
|
query_stats = []
|
244
249
|
grouped_stats.each do |_, stats2|
|
245
250
|
value = {
|
246
251
|
"query" => (stats2.find { |s| s["query"] } || {})["query"],
|
252
|
+
"user" => (stats2.find { |s| s["user"] } || {})["user"],
|
247
253
|
"query_hash" => (stats2.find { |s| s["query"] } || {})["query_hash"],
|
248
254
|
"total_minutes" => stats2.sum { |s| s["total_minutes"].to_f },
|
249
255
|
"calls" => stats2.sum { |s| s["calls"].to_i },
|
data/lib/pghero/methods/space.rb
CHANGED
@@ -11,7 +11,8 @@ module PgHero
|
|
11
11
|
n.nspname AS schema,
|
12
12
|
c.relname AS name,
|
13
13
|
CASE WHEN c.relkind = 'r' THEN 'table' ELSE 'index' END AS type,
|
14
|
-
pg_size_pretty(pg_table_size(c.oid)) AS size
|
14
|
+
pg_size_pretty(pg_table_size(c.oid)) AS size,
|
15
|
+
pg_table_size(c.oid) AS size_bytes
|
15
16
|
FROM
|
16
17
|
pg_class c
|
17
18
|
LEFT JOIN
|
@@ -25,6 +26,16 @@ module PgHero
|
|
25
26
|
name ASC
|
26
27
|
SQL
|
27
28
|
end
|
29
|
+
|
30
|
+
def capture_space_stats
|
31
|
+
now = Time.now
|
32
|
+
columns = %w[database schema relation size captured_at]
|
33
|
+
values = []
|
34
|
+
relation_sizes.each do |rs|
|
35
|
+
values << [id, rs["schema"], rs["name"], rs["size_bytes"].to_i, now]
|
36
|
+
end
|
37
|
+
insert_stats("pghero_space_stats", columns, values)
|
38
|
+
end
|
28
39
|
end
|
29
40
|
end
|
30
41
|
end
|
@@ -188,7 +188,9 @@ module PgHero
|
|
188
188
|
return {error: "Too large"} if statement.to_s.length > 10000
|
189
189
|
|
190
190
|
begin
|
191
|
-
|
191
|
+
parsed_statement = PgQuery.parse(statement)
|
192
|
+
v2 = parsed_statement.respond_to?(:tree)
|
193
|
+
tree = v2 ? parsed_statement.tree : parsed_statement.parsetree
|
192
194
|
rescue PgQuery::ParseError
|
193
195
|
return {error: "Parse error"}
|
194
196
|
end
|
@@ -199,10 +201,14 @@ module PgHero
|
|
199
201
|
unless table
|
200
202
|
error =
|
201
203
|
case tree.keys.first
|
202
|
-
when "INSERT INTO"
|
204
|
+
when "InsertStmt", "INSERT INTO"
|
203
205
|
"INSERT statement"
|
204
|
-
when "SET"
|
206
|
+
when "VariableSetStmt", "SET"
|
205
207
|
"SET statement"
|
208
|
+
when "SelectStmt"
|
209
|
+
if (tree["SelectStmt"]["fromClause"].first["JoinExpr"] rescue false)
|
210
|
+
"JOIN not supported yet"
|
211
|
+
end
|
206
212
|
when "SELECT"
|
207
213
|
if (tree["SELECT"]["fromClause"].first["JOINEXPR"] rescue false)
|
208
214
|
"JOIN not supported yet"
|
@@ -211,11 +217,11 @@ module PgHero
|
|
211
217
|
return {error: error || "Unknown structure"}
|
212
218
|
end
|
213
219
|
|
214
|
-
select = tree
|
215
|
-
where = (select["whereClause"] ? parse_where(select["whereClause"]) : []) rescue nil
|
220
|
+
select = tree.values.first
|
221
|
+
where = (select["whereClause"] ? parse_where(select["whereClause"], v2) : []) rescue nil
|
216
222
|
return {error: "Unknown structure"} unless where
|
217
223
|
|
218
|
-
sort = (select["sortClause"] ? parse_sort(select["sortClause"]) : []) rescue []
|
224
|
+
sort = (select["sortClause"] ? parse_sort(select["sortClause"], v2) : []) rescue []
|
219
225
|
|
220
226
|
{table: table, where: where, sort: sort}
|
221
227
|
end
|
@@ -260,6 +266,12 @@ module PgHero
|
|
260
266
|
|
261
267
|
def parse_table(tree)
|
262
268
|
case tree.keys.first
|
269
|
+
when "SelectStmt"
|
270
|
+
tree["SelectStmt"]["fromClause"].first["RangeVar"]["relname"]
|
271
|
+
when "DeleteStmt"
|
272
|
+
tree["DeleteStmt"]["relation"]["RangeVar"]["relname"]
|
273
|
+
when "UpdateStmt"
|
274
|
+
tree["UpdateStmt"]["relation"]["RangeVar"]["relname"]
|
263
275
|
when "SELECT"
|
264
276
|
tree["SELECT"]["fromClause"].first["RANGEVAR"]["relname"]
|
265
277
|
when "DELETE FROM"
|
@@ -270,41 +282,69 @@ module PgHero
|
|
270
282
|
end
|
271
283
|
|
272
284
|
# TODO capture values
|
273
|
-
def parse_where(tree)
|
274
|
-
|
285
|
+
def parse_where(tree, v2 = false)
|
286
|
+
if v2
|
287
|
+
aexpr = tree["A_Expr"]
|
275
288
|
|
276
|
-
|
277
|
-
|
278
|
-
|
289
|
+
if tree["BoolExpr"]
|
290
|
+
if tree["BoolExpr"]["boolop"] == 0
|
291
|
+
tree["BoolExpr"]["args"].flat_map { |v| parse_where(v, v2) }
|
292
|
+
else
|
293
|
+
raise "Not Implemented"
|
294
|
+
end
|
295
|
+
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first["String"]["str"])
|
296
|
+
[{column: aexpr["lexpr"]["ColumnRef"]["fields"].last["String"]["str"], op: aexpr["name"].first["String"]["str"]}]
|
297
|
+
elsif tree["NullTest"]
|
298
|
+
op = tree["NullTest"]["nulltesttype"] == 1 ? "not_null" : "null"
|
299
|
+
[{column: tree["NullTest"]["arg"]["ColumnRef"]["fields"].last["String"]["str"], op: op}]
|
279
300
|
else
|
280
301
|
raise "Not Implemented"
|
281
302
|
end
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
if
|
286
|
-
|
303
|
+
else
|
304
|
+
aexpr = tree["AEXPR"] || tree[nil]
|
305
|
+
|
306
|
+
if tree["BOOLEXPR"]
|
307
|
+
if tree["BOOLEXPR"]["boolop"] == 0
|
308
|
+
tree["BOOLEXPR"]["args"].flat_map { |v| parse_where(v) }
|
309
|
+
else
|
310
|
+
raise "Not Implemented"
|
311
|
+
end
|
312
|
+
elsif tree["AEXPR AND"]
|
313
|
+
left = parse_where(tree["AEXPR AND"]["lexpr"])
|
314
|
+
right = parse_where(tree["AEXPR AND"]["rexpr"])
|
315
|
+
if left && right
|
316
|
+
left + right
|
317
|
+
else
|
318
|
+
raise "Not Implemented"
|
319
|
+
end
|
320
|
+
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first)
|
321
|
+
[{column: aexpr["lexpr"]["COLUMNREF"]["fields"].last, op: aexpr["name"].first}]
|
322
|
+
elsif tree["AEXPR IN"] && ["=", "<>"].include?(tree["AEXPR IN"]["name"].first)
|
323
|
+
[{column: tree["AEXPR IN"]["lexpr"]["COLUMNREF"]["fields"].last, op: tree["AEXPR IN"]["name"].first}]
|
324
|
+
elsif tree["NULLTEST"]
|
325
|
+
op = tree["NULLTEST"]["nulltesttype"] == 1 ? "not_null" : "null"
|
326
|
+
[{column: tree["NULLTEST"]["arg"]["COLUMNREF"]["fields"].last, op: op}]
|
287
327
|
else
|
288
328
|
raise "Not Implemented"
|
289
329
|
end
|
290
|
-
elsif aexpr && ["=", "<>", ">", ">=", "<", "<=", "~~", "~~*", "BETWEEN"].include?(aexpr["name"].first)
|
291
|
-
[{column: aexpr["lexpr"]["COLUMNREF"]["fields"].last, op: aexpr["name"].first}]
|
292
|
-
elsif tree["AEXPR IN"] && ["=", "<>"].include?(tree["AEXPR IN"]["name"].first)
|
293
|
-
[{column: tree["AEXPR IN"]["lexpr"]["COLUMNREF"]["fields"].last, op: tree["AEXPR IN"]["name"].first}]
|
294
|
-
elsif tree["NULLTEST"]
|
295
|
-
op = tree["NULLTEST"]["nulltesttype"] == 1 ? "not_null" : "null"
|
296
|
-
[{column: tree["NULLTEST"]["arg"]["COLUMNREF"]["fields"].last, op: op}]
|
297
|
-
else
|
298
|
-
raise "Not Implemented"
|
299
330
|
end
|
300
331
|
end
|
301
332
|
|
302
|
-
def parse_sort(sort_clause)
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
333
|
+
def parse_sort(sort_clause, v2)
|
334
|
+
if v2
|
335
|
+
sort_clause.map do |v|
|
336
|
+
{
|
337
|
+
column: v["SortBy"]["node"]["ColumnRef"]["fields"].last["String"]["str"],
|
338
|
+
direction: v["SortBy"]["sortby_dir"] == 2 ? "desc" : "asc"
|
339
|
+
}
|
340
|
+
end
|
341
|
+
else
|
342
|
+
sort_clause.map do |v|
|
343
|
+
{
|
344
|
+
column: v["SORTBY"]["node"]["COLUMNREF"]["fields"].last,
|
345
|
+
direction: v["SORTBY"]["sortby_dir"] == 2 ? "desc" : "asc"
|
346
|
+
}
|
347
|
+
end
|
308
348
|
end
|
309
349
|
end
|
310
350
|
|