sqlui 0.1.47 → 0.1.48
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/.version +1 -1
- data/app/database_config.rb +28 -2
- data/app/server.rb +17 -9
- data/app/sql_parser.rb +74 -10
- data/app/views/error.erb +21 -0
- data/client/resources/favicon.svg +1 -0
- data/client/resources/sqlui.css +26 -3
- data/client/resources/sqlui.html +1 -0
- data/client/resources/sqlui.js +230 -203
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddbafc23478bb1d27023d516dda3ef412306c68e3084aa9c7e186c7372ff18ad
|
|
4
|
+
data.tar.gz: 5638fa4d6a7e30ecce0d89505953e2fc2d9f18e19e928c41f7c1bde4a986aba9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c99d375bf14a2f892b1a7c3e85ec576ebdc749e432e56f7e993c8437c89e8a19af96189a501b7a80e08e3b6f5743eeecd57dbfa94b056570ba1da0274d163121
|
|
7
|
+
data.tar.gz: 3fab12e8be52afbd23f317b16c48eed4d095f293f4bab02a9c4c515ea91d501fd027fa71b1f008831d86fa00af38fb4376ba6c3f891ba5a4c7aa7f0b39690e2c
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.48
|
data/app/database_config.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative 'args'
|
|
|
8
8
|
|
|
9
9
|
# Config for a single database.
|
|
10
10
|
class DatabaseConfig
|
|
11
|
-
attr_reader :display_name, :description, :url_path, :joins, :saved_path, :tables, :client_params
|
|
11
|
+
attr_reader :display_name, :description, :url_path, :joins, :saved_path, :tables, :columns, :client_params
|
|
12
12
|
|
|
13
13
|
def initialize(hash)
|
|
14
14
|
@display_name = Args.fetch_non_empty_string(hash, :display_name).strip
|
|
@@ -28,7 +28,7 @@ class DatabaseConfig
|
|
|
28
28
|
raise ArgumentError, "invalid join #{join.to_json}"
|
|
29
29
|
end
|
|
30
30
|
@tables = Args.fetch_optional_hash(hash, :tables) || {}
|
|
31
|
-
@tables
|
|
31
|
+
@tables.each do |table, table_config|
|
|
32
32
|
unless table_config.is_a?(Hash)
|
|
33
33
|
raise ArgumentError, "invalid table config for #{table} (#{table_config}), expected hash"
|
|
34
34
|
end
|
|
@@ -43,6 +43,32 @@ class DatabaseConfig
|
|
|
43
43
|
raise ArgumentError, "invalid table boost for #{table} (#{table_boost}), expected int"
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
@columns = Args.fetch_optional_hash(hash, :columns) || {}
|
|
47
|
+
@columns.each do |column, column_config|
|
|
48
|
+
unless column_config.is_a?(Hash)
|
|
49
|
+
raise ArgumentError, "invalid column config for #{column} (#{column_config}), expected hash"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
links = column_config[:links]
|
|
53
|
+
raise ArgumentError, "invalid links for #{column} (#{links}), expected array" if links && !links.is_a?(Array)
|
|
54
|
+
|
|
55
|
+
links.each do |link_config|
|
|
56
|
+
unless link_config.is_a?(Hash)
|
|
57
|
+
raise ArgumentError, "invalid link config for #{column} (#{link_config}), expected hash"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
unless link_config[:short_name].is_a?(String)
|
|
61
|
+
raise ArgumentError,
|
|
62
|
+
"invalid link short_name for #{column} link (#{link_config[:short_name]}), expected string"
|
|
63
|
+
end
|
|
64
|
+
unless link_config[:long_name].is_a?(String)
|
|
65
|
+
raise ArgumentError, "invalid link long_name for #{column} link (#{link_config[:long_name]}), expected string"
|
|
66
|
+
end
|
|
67
|
+
unless link_config[:template].is_a?(String)
|
|
68
|
+
raise ArgumentError, "invalid template for #{column} link (#{link_config[:template]}), expected string"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
46
72
|
aliases = @tables.map { |_table, table_config| table_config[:alias] }.compact
|
|
47
73
|
if aliases.to_set.size < aliases.size
|
|
48
74
|
duplicate_aliases = aliases.reject { |a| aliases.count(a) == 1 }.to_set
|
data/app/server.rb
CHANGED
|
@@ -39,6 +39,10 @@ class Server < Sinatra::Base
|
|
|
39
39
|
redirect config.list_url_path, 301
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
get '/favicon.svg' do
|
|
43
|
+
send_file File.join(resources_dir, 'favicon.svg')
|
|
44
|
+
end
|
|
45
|
+
|
|
42
46
|
get "#{config.list_url_path}/?" do
|
|
43
47
|
erb :databases, locals: { config: config }
|
|
44
48
|
end
|
|
@@ -69,6 +73,7 @@ class Server < Sinatra::Base
|
|
|
69
73
|
list_url_path: config.list_url_path,
|
|
70
74
|
schemas: DatabaseMetadata.lookup(client, database),
|
|
71
75
|
tables: database.tables,
|
|
76
|
+
columns: database.columns,
|
|
72
77
|
joins: database.joins,
|
|
73
78
|
saved: Dir.glob("#{database.saved_path}/*.sql").to_h do |path|
|
|
74
79
|
contents = File.read(path)
|
|
@@ -163,15 +168,18 @@ class Server < Sinatra::Base
|
|
|
163
168
|
end
|
|
164
169
|
end
|
|
165
170
|
|
|
166
|
-
error do
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
error 400..510 do
|
|
172
|
+
exception = env['sinatra.error']
|
|
173
|
+
stacktrace = exception&.full_message(highlight: false)
|
|
174
|
+
if request.env['HTTP_ACCEPT'] == 'application/json'
|
|
175
|
+
headers 'Content-Type' => 'application/json; charset=utf-8'
|
|
176
|
+
message = exception&.message&.lines&.first&.strip || 'unexpected error'
|
|
177
|
+
json = { error: message, stacktrace: stacktrace }.compact.to_json
|
|
178
|
+
body json
|
|
179
|
+
else
|
|
180
|
+
message = "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
|
|
181
|
+
erb :error, locals: { title: "SQLUI #{message}", message: message, stacktrace: stacktrace }
|
|
182
|
+
end
|
|
175
183
|
end
|
|
176
184
|
|
|
177
185
|
run!
|
data/app/sql_parser.rb
CHANGED
|
@@ -1,19 +1,83 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
3
5
|
# Used to parse strings containing one or more SQL statements.
|
|
4
6
|
class SqlParser
|
|
5
7
|
def self.find_statement_at_cursor(sql, cursor)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
parts_with_ranges = []
|
|
9
|
-
sql.scan(/[^;]*;[ \n]*/) { |part| parts_with_ranges << [part, 0, part.size] }
|
|
10
|
-
parts_with_ranges.inject(0) do |pos, current|
|
|
11
|
-
current[1] += pos
|
|
12
|
-
current[2] += pos
|
|
13
|
-
end
|
|
8
|
+
parts_with_ranges = parse(sql)
|
|
14
9
|
part_with_range = parts_with_ranges.find do |current|
|
|
15
|
-
cursor >= current[1] && cursor
|
|
10
|
+
cursor >= current[1] && cursor <= current[2]
|
|
16
11
|
end || parts_with_ranges[-1]
|
|
17
|
-
part_with_range[0]
|
|
12
|
+
part_with_range[0]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.parse(sql)
|
|
16
|
+
scanner = StringScanner.new(sql)
|
|
17
|
+
statements = []
|
|
18
|
+
single_quoted = false
|
|
19
|
+
double_quoted = false
|
|
20
|
+
single_commented = false
|
|
21
|
+
multi_commented = false
|
|
22
|
+
escaped = false
|
|
23
|
+
current = ''
|
|
24
|
+
start = 0
|
|
25
|
+
until scanner.eos?
|
|
26
|
+
current ||= ''
|
|
27
|
+
char = scanner.getch
|
|
28
|
+
current += char
|
|
29
|
+
|
|
30
|
+
if (single_quoted || double_quoted) && !escaped && char == "'" && scanner.peek(1) == "'"
|
|
31
|
+
current += scanner.getch
|
|
32
|
+
elsif escaped
|
|
33
|
+
escaped = false
|
|
34
|
+
elsif !single_quoted && !double_quoted && !escaped && !single_commented && char == '/' && scanner.peek(1) == '*'
|
|
35
|
+
current += scanner.getch
|
|
36
|
+
multi_commented = true
|
|
37
|
+
elsif multi_commented && char == '*' && scanner.peek(1) == '/'
|
|
38
|
+
multi_commented = false
|
|
39
|
+
elsif multi_commented
|
|
40
|
+
next
|
|
41
|
+
elsif !single_quoted && !double_quoted && !escaped && !multi_commented &&
|
|
42
|
+
char == '-' && scanner.peek(1).match?(/-[ \n\t]/)
|
|
43
|
+
current += scanner.getch
|
|
44
|
+
single_commented = true
|
|
45
|
+
elsif !single_quoted && !double_quoted && !escaped && !multi_commented && char == '#'
|
|
46
|
+
single_commented = true
|
|
47
|
+
elsif single_commented && char == "\n"
|
|
48
|
+
single_commented = false
|
|
49
|
+
elsif single_commented
|
|
50
|
+
single_quoted = false if char == "\n"
|
|
51
|
+
elsif char == '\\'
|
|
52
|
+
escaped = true
|
|
53
|
+
elsif char == "'"
|
|
54
|
+
single_quoted = !single_quoted unless double_quoted
|
|
55
|
+
elsif char == '"'
|
|
56
|
+
double_quoted = !double_quoted unless single_quoted
|
|
57
|
+
elsif char == ';' && !single_quoted && !double_quoted
|
|
58
|
+
# Include trailing whitespace if it runs to end of line
|
|
59
|
+
current += scanner.scan(/[ \t]*(?=\n)/) || ''
|
|
60
|
+
|
|
61
|
+
# Include an optional trailing single line comma
|
|
62
|
+
current += scanner.scan(/[ \t]*--[ \t][^\n]*/) || ''
|
|
63
|
+
current += scanner.scan(/[ \t]*#[^\n]*/) || ''
|
|
64
|
+
|
|
65
|
+
# Include any following blank lines, careful to avoid the final newline in case it is the start of a new query
|
|
66
|
+
while (more = scanner.scan(/\n[ \t]*(?=\n)/))
|
|
67
|
+
current += more
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Include remaining whitespace if that's all that's left
|
|
71
|
+
if scanner.rest_size == scanner.match?(/[ \t\n]*/)
|
|
72
|
+
current += scanner.rest
|
|
73
|
+
scanner.terminate
|
|
74
|
+
end
|
|
75
|
+
statements << [current, start, scanner.pos]
|
|
76
|
+
start = scanner.pos
|
|
77
|
+
current = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
statements << [current, start, scanner.pos] unless current.nil?
|
|
81
|
+
statements
|
|
18
82
|
end
|
|
19
83
|
end
|
data/app/views/error.erb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title><%= title %></title>
|
|
6
|
+
<link rel="icon" type="image/x-icon" href="/favicon.svg">
|
|
7
|
+
</head>
|
|
8
|
+
<body style="font-family: monospace; font-size: 16px;">
|
|
9
|
+
|
|
10
|
+
<% if defined?(message) && message %>
|
|
11
|
+
<p><%= message %></p>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<% if defined?(stacktrace) && stacktrace %>
|
|
15
|
+
<pre>
|
|
16
|
+
<%= stacktrace %>
|
|
17
|
+
</pre>
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg version="1.1" viewBox="0.0 0.0 32.0 32.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l32.0 0l0 32.0l-32.0 0l0 -32.0z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l32.0 0l0 32.0l-32.0 0z" fill-rule="evenodd"/><path fill="#6d9eeb" d="m1.3070866 20.064245l0 0c0 1.6235943 6.5782413 2.9397774 14.692913 2.9397774c8.114672 0 14.692913 -1.3161831 14.692913 -2.9397774l0 8.073204c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774z" fill-rule="evenodd"/><path fill="#a7c4f3" d="m1.3070866 20.064245l0 0c0 -1.6235924 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161831 14.692913 2.9397755l0 0c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m30.692913 20.064245l0 0c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774l0 0c0 -1.6235924 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161831 14.692913 2.9397755l0 8.073204c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774l0 -8.073204" fill-rule="evenodd"/><path stroke="#351c75" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m30.692913 20.064245l0 0c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774l0 0c0 -1.6235924 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161831 14.692913 2.9397755l0 8.073204c0 1.6235943 -6.5782413 2.9397774 -14.692913 2.9397774c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397774l0 -8.073204" fill-rule="evenodd"/><path fill="#93c47d" d="m1.3070866 12.012567l0 0c0 1.6235943 6.5782413 2.9397764 14.692913 2.9397764c8.114672 0 14.692913 -1.3161821 14.692913 -2.9397764l0 8.073205c0 1.6235924 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397755z" fill-rule="evenodd"/><path fill="#bedbb1" d="m1.3070866 12.012567l0 0c0 -1.6235933 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161821 14.692913 2.9397755l0 0c0 1.6235943 -6.5782413 2.9397764 -14.692913 2.9397764c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397764z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m30.692913 12.012567l0 0c0 1.6235943 -6.5782413 2.9397764 -14.692913 2.9397764c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397764l0 0c0 -1.6235933 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161821 14.692913 2.9397755l0 8.073205c0 1.6235924 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397755l0 -8.073205" fill-rule="evenodd"/><path stroke="#351c75" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m30.692913 12.012567l0 0c0 1.6235943 -6.5782413 2.9397764 -14.692913 2.9397764c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397764l0 0c0 -1.6235933 6.5782413 -2.9397755 14.692913 -2.9397755c8.114672 0 14.692913 1.3161821 14.692913 2.9397755l0 8.073205c0 1.6235924 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161831 -14.692913 -2.9397755l0 -8.073205" fill-rule="evenodd"/><path fill="#f4cccc" d="m1.3070866 3.862348l0 0c0 1.6235933 6.5782413 2.939776 14.692913 2.939776c8.114672 0 14.692913 -1.3161826 14.692913 -2.939776l0 8.073204c0 1.6235933 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397755z" fill-rule="evenodd"/><path fill="#f8e0e0" d="m1.3070866 3.862348l0 0c0 -1.6235933 6.5782413 -2.939776 14.692913 -2.939776c8.114672 0 14.692913 1.3161826 14.692913 2.939776l0 0c0 1.6235933 -6.5782413 2.939776 -14.692913 2.939776c-8.114672 0 -14.692913 -1.3161826 -14.692913 -2.939776z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m30.692913 3.862348l0 0c0 1.6235933 -6.5782413 2.939776 -14.692913 2.939776c-8.114672 0 -14.692913 -1.3161826 -14.692913 -2.939776l0 0c0 -1.6235933 6.5782413 -2.939776 14.692913 -2.939776c8.114672 0 14.692913 1.3161826 14.692913 2.939776l0 8.073204c0 1.6235933 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397755l0 -8.073204" fill-rule="evenodd"/><path stroke="#351c75" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m30.692913 3.862348l0 0c0 1.6235933 -6.5782413 2.939776 -14.692913 2.939776c-8.114672 0 -14.692913 -1.3161826 -14.692913 -2.939776l0 0c0 -1.6235933 6.5782413 -2.939776 14.692913 -2.939776c8.114672 0 14.692913 1.3161826 14.692913 2.939776l0 8.073204c0 1.6235933 -6.5782413 2.9397755 -14.692913 2.9397755c-8.114672 0 -14.692913 -1.3161821 -14.692913 -2.9397755l0 -8.073204" fill-rule="evenodd"/></g></svg>
|
data/client/resources/sqlui.css
CHANGED
|
@@ -158,7 +158,7 @@ p {
|
|
|
158
158
|
border-left: 1px solid #888;
|
|
159
159
|
border-right: 1px solid #888;
|
|
160
160
|
border-bottom: 1px solid #888;
|
|
161
|
-
z-index:
|
|
161
|
+
z-index: 2;
|
|
162
162
|
flex-direction: column;
|
|
163
163
|
left: 0;
|
|
164
164
|
right: 0;
|
|
@@ -246,6 +246,29 @@ p {
|
|
|
246
246
|
flex-direction: column;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
#result-table tbody tr td abbr a {
|
|
250
|
+
color: #555;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
text-decoration: none;
|
|
253
|
+
margin: 0;
|
|
254
|
+
padding: 0 3px;
|
|
255
|
+
border: 1px dotted #555;
|
|
256
|
+
font-size: 12px;
|
|
257
|
+
display: inline-block;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#result-table tbody tr td abbr {
|
|
261
|
+
margin: 0 0 0 5px;
|
|
262
|
+
text-decoration: none;
|
|
263
|
+
display: inline-block;
|
|
264
|
+
position: relative;
|
|
265
|
+
top: -2px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#result-table tbody tr td abbr:first-child {
|
|
269
|
+
margin: 0 0 0 10px;
|
|
270
|
+
}
|
|
271
|
+
|
|
249
272
|
.fetch-sql-box {
|
|
250
273
|
justify-content: center;
|
|
251
274
|
align-items: center;
|
|
@@ -271,7 +294,7 @@ table td:last-child, table th:last-child {
|
|
|
271
294
|
}
|
|
272
295
|
|
|
273
296
|
td, th {
|
|
274
|
-
padding: 5px
|
|
297
|
+
padding: 5px 10px;
|
|
275
298
|
font-weight: normal;
|
|
276
299
|
white-space: nowrap;
|
|
277
300
|
max-width: 500px;
|
|
@@ -300,7 +323,7 @@ thead {
|
|
|
300
323
|
position: -webkit-sticky;
|
|
301
324
|
position: sticky;
|
|
302
325
|
top: 0;
|
|
303
|
-
z-index:
|
|
326
|
+
z-index: 1;
|
|
304
327
|
table-layout: fixed;
|
|
305
328
|
}
|
|
306
329
|
|
data/client/resources/sqlui.html
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<head>
|
|
3
3
|
<meta charset="utf-8">
|
|
4
4
|
<title>SQLUI</title>
|
|
5
|
+
<link rel="icon" type="image/x-icon" href="/favicon.svg">
|
|
5
6
|
<script src="sqlui.js"></script>
|
|
6
7
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
|
7
8
|
<link rel="stylesheet" href="sqlui.css">
|
data/client/resources/sqlui.js
CHANGED
|
@@ -21666,6 +21666,57 @@
|
|
|
21666
21666
|
}
|
|
21667
21667
|
});
|
|
21668
21668
|
|
|
21669
|
+
function base64Encode (str) {
|
|
21670
|
+
// https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
|
|
21671
|
+
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
|
21672
|
+
function toSolidBytes (match, p1) {
|
|
21673
|
+
return String.fromCharCode(Number(`0x${p1}`))
|
|
21674
|
+
}))
|
|
21675
|
+
}
|
|
21676
|
+
|
|
21677
|
+
function copyTextToClipboard (text) {
|
|
21678
|
+
const type = 'text/plain';
|
|
21679
|
+
const blob = new Blob([text], { type });
|
|
21680
|
+
navigator.clipboard.write([new window.ClipboardItem({ [type]: blob })]);
|
|
21681
|
+
}
|
|
21682
|
+
|
|
21683
|
+
function toCsv (columns, rows) {
|
|
21684
|
+
let text = columns.map((header) => {
|
|
21685
|
+
if (typeof header === 'string' && (header.includes('"') || header.includes(','))) {
|
|
21686
|
+
return `"${header.replaceAll('"', '""')}"`
|
|
21687
|
+
} else {
|
|
21688
|
+
return header
|
|
21689
|
+
}
|
|
21690
|
+
}).join(',') + '\n';
|
|
21691
|
+
text += rows.map((row) => {
|
|
21692
|
+
return row.map((cell) => {
|
|
21693
|
+
if (typeof cell === 'string' && (cell.includes('"') || cell.includes(','))) {
|
|
21694
|
+
return `"${cell.replaceAll('"', '""')}"`
|
|
21695
|
+
} else {
|
|
21696
|
+
return cell
|
|
21697
|
+
}
|
|
21698
|
+
}).join(',')
|
|
21699
|
+
}).join('\n');
|
|
21700
|
+
|
|
21701
|
+
return text
|
|
21702
|
+
}
|
|
21703
|
+
|
|
21704
|
+
function toTsv (columns, rows) {
|
|
21705
|
+
if (columns.find((cell) => cell === '\t')) {
|
|
21706
|
+
throw new Error('TSV input may not contain a tab character.')
|
|
21707
|
+
}
|
|
21708
|
+
let text = columns.join('\t') + '\n';
|
|
21709
|
+
|
|
21710
|
+
text += rows.map((row) => {
|
|
21711
|
+
if (row.find((cell) => cell === '\t')) {
|
|
21712
|
+
throw new Error('TSV input may not contain a tab character.')
|
|
21713
|
+
}
|
|
21714
|
+
return row.join('\t')
|
|
21715
|
+
}).join('\n');
|
|
21716
|
+
|
|
21717
|
+
return text
|
|
21718
|
+
}
|
|
21719
|
+
|
|
21669
21720
|
/// A parse stack. These are used internally by the parser to track
|
|
21670
21721
|
/// parsing progress. They also provide some properties and methods
|
|
21671
21722
|
/// that external code such as a tokenizer can use to get information
|
|
@@ -23860,131 +23911,7 @@
|
|
|
23860
23911
|
builtin: MySQLBuiltin
|
|
23861
23912
|
});
|
|
23862
23913
|
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
function unquoteSqlId (identifier) {
|
|
23866
|
-
const match = identifier.match(/^`(.*)`$/);
|
|
23867
|
-
return match ? match[1] : identifier
|
|
23868
|
-
}
|
|
23869
|
-
|
|
23870
|
-
function base64Encode (str) {
|
|
23871
|
-
// https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
|
|
23872
|
-
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
|
23873
|
-
function toSolidBytes (match, p1) {
|
|
23874
|
-
return String.fromCharCode(Number(`0x${p1}`))
|
|
23875
|
-
}))
|
|
23876
|
-
}
|
|
23877
|
-
|
|
23878
|
-
function getSqlFromUrl (url) {
|
|
23879
|
-
const params = url.searchParams;
|
|
23880
|
-
if (params.has('file') && params.has('sql')) {
|
|
23881
|
-
// TODO: show an error.
|
|
23882
|
-
throw new Error('You can only specify a file or sql param, not both.')
|
|
23883
|
-
}
|
|
23884
|
-
if (params.has('sql')) {
|
|
23885
|
-
return params.get('sql')
|
|
23886
|
-
} else if (params.has('file')) {
|
|
23887
|
-
const file = params.get('file');
|
|
23888
|
-
const fileDetails = window.metadata.saved[file];
|
|
23889
|
-
if (!fileDetails) throw new Error(`no such file: ${file}`)
|
|
23890
|
-
return fileDetails.contents
|
|
23891
|
-
}
|
|
23892
|
-
throw new Error('You must specify a file or sql param')
|
|
23893
|
-
}
|
|
23894
|
-
|
|
23895
|
-
function init (parent, onSubmit, onShiftSubmit) {
|
|
23896
|
-
addClickListener(document.getElementById('query-tab-button'), (event) => selectTab(event, 'query'));
|
|
23897
|
-
addClickListener(document.getElementById('saved-tab-button'), (event) => selectTab(event, 'saved'));
|
|
23898
|
-
addClickListener(document.getElementById('structure-tab-button'), (event) => selectTab(event, 'structure'));
|
|
23899
|
-
addClickListener(document.getElementById('graph-tab-button'), (event) => selectTab(event, 'graph'));
|
|
23900
|
-
addClickListener(document.getElementById('cancel-button'), (event) => clearResult());
|
|
23901
|
-
|
|
23902
|
-
const dropdownContent = document.getElementById('submit-dropdown-content');
|
|
23903
|
-
const dropdownButton = document.getElementById('submit-dropdown-button');
|
|
23904
|
-
addClickListener(dropdownButton, () => dropdownContent.classList.toggle('submit-dropdown-content-show'));
|
|
23905
|
-
|
|
23906
|
-
const isMac = navigator.userAgent.includes('Mac');
|
|
23907
|
-
const runCurrentLabel = `run selection (${isMac ? '⌘' : 'Ctrl'}-Enter)`;
|
|
23908
|
-
const runAllLabel = `run all (${isMac ? '⌘' : 'Ctrl'}-Shift-Enter)`;
|
|
23909
|
-
|
|
23910
|
-
const submitButtonCurrent = document.getElementById('submit-button-current');
|
|
23911
|
-
submitButtonCurrent.value = runCurrentLabel;
|
|
23912
|
-
addClickListener(submitButtonCurrent, (event) => submitCurrent(event.target, event));
|
|
23913
|
-
|
|
23914
|
-
const submitButtonAll = document.getElementById('submit-button-all');
|
|
23915
|
-
submitButtonAll.value = runAllLabel;
|
|
23916
|
-
addClickListener(submitButtonAll, (event) => submitAll(event.target, event));
|
|
23917
|
-
|
|
23918
|
-
const dropdownButtonCurrent = document.getElementById('submit-dropdown-button-current');
|
|
23919
|
-
dropdownButtonCurrent.value = runCurrentLabel;
|
|
23920
|
-
addClickListener(dropdownButtonCurrent, (event) => submitCurrent(event.target, event));
|
|
23921
|
-
|
|
23922
|
-
const dropdownAllButton = document.getElementById('submit-dropdown-button-all');
|
|
23923
|
-
dropdownAllButton.value = runAllLabel;
|
|
23924
|
-
addClickListener(dropdownAllButton, (event) => submitAll(event.target, event));
|
|
23925
|
-
|
|
23926
|
-
const dropdownToggleButton = document.getElementById('submit-dropdown-button-toggle');
|
|
23927
|
-
addClickListener(dropdownToggleButton, () => {
|
|
23928
|
-
submitButtonCurrent.classList.toggle('submit-button-show');
|
|
23929
|
-
submitButtonAll.classList.toggle('submit-button-show');
|
|
23930
|
-
focus(getSelection());
|
|
23931
|
-
});
|
|
23932
|
-
|
|
23933
|
-
const copyListenerFactory = (delimiter) => {
|
|
23934
|
-
return () => {
|
|
23935
|
-
if (!window.sqlFetch?.result) {
|
|
23936
|
-
return
|
|
23937
|
-
}
|
|
23938
|
-
const type = 'text/plain';
|
|
23939
|
-
let text = window.sqlFetch.result.columns.map((header) => {
|
|
23940
|
-
if (typeof header === 'string' && (header.includes('"') || header.includes(delimiter))) {
|
|
23941
|
-
return `"${header.replaceAll('"', '""')}"`
|
|
23942
|
-
} else {
|
|
23943
|
-
return header
|
|
23944
|
-
}
|
|
23945
|
-
}).join(delimiter) + '\n';
|
|
23946
|
-
text += window.sqlFetch.result.rows.map((row) => {
|
|
23947
|
-
return row.map((cell) => {
|
|
23948
|
-
if (typeof cell === 'string' && (cell.includes('"') || cell.includes(delimiter))) {
|
|
23949
|
-
return `"${cell.replaceAll('"', '""')}"`
|
|
23950
|
-
} else {
|
|
23951
|
-
return cell
|
|
23952
|
-
}
|
|
23953
|
-
}).join(delimiter)
|
|
23954
|
-
}).join('\n');
|
|
23955
|
-
const blob = new Blob([text], { type });
|
|
23956
|
-
navigator.clipboard.write([new window.ClipboardItem({ [type]: blob })]);
|
|
23957
|
-
}
|
|
23958
|
-
};
|
|
23959
|
-
addClickListener(document.getElementById('submit-dropdown-button-copy-csv'), copyListenerFactory(','));
|
|
23960
|
-
addClickListener(document.getElementById('submit-dropdown-button-copy-tsv'), copyListenerFactory('\t'));
|
|
23961
|
-
addClickListener(document.getElementById('submit-dropdown-button-download-csv'), () => {
|
|
23962
|
-
if (!window.sqlFetch?.result) return
|
|
23963
|
-
|
|
23964
|
-
const url = new URL(window.location);
|
|
23965
|
-
url.searchParams.set('sql', base64Encode(getSqlFromUrl(url)));
|
|
23966
|
-
url.searchParams.delete('file');
|
|
23967
|
-
setActionInUrl(url, 'download_csv');
|
|
23968
|
-
|
|
23969
|
-
const link = document.createElement('a');
|
|
23970
|
-
link.setAttribute('download', 'result.csv');
|
|
23971
|
-
link.setAttribute('href', url.href);
|
|
23972
|
-
link.click();
|
|
23973
|
-
|
|
23974
|
-
focus(getSelection());
|
|
23975
|
-
});
|
|
23976
|
-
|
|
23977
|
-
document.addEventListener('click', function (event) {
|
|
23978
|
-
if (event.target !== dropdownButton) {
|
|
23979
|
-
dropdownContent.classList.remove('submit-dropdown-content-show');
|
|
23980
|
-
}
|
|
23981
|
-
});
|
|
23982
|
-
dropdownContent.addEventListener('focusout', function (event) {
|
|
23983
|
-
if (!dropdownContent.contains(event.relatedTarget)) {
|
|
23984
|
-
dropdownContent.classList.remove('submit-dropdown-content-show');
|
|
23985
|
-
}
|
|
23986
|
-
});
|
|
23987
|
-
|
|
23914
|
+
function createEditor (parent, metadata, onSubmit, onShiftSubmit) {
|
|
23988
23915
|
const fixedHeightEditor = EditorView.theme({
|
|
23989
23916
|
'.cm-scroller': {
|
|
23990
23917
|
height: '200px',
|
|
@@ -23992,7 +23919,7 @@
|
|
|
23992
23919
|
resize: 'vertical'
|
|
23993
23920
|
}
|
|
23994
23921
|
});
|
|
23995
|
-
const schemas = Object.entries(
|
|
23922
|
+
const schemas = Object.entries(metadata.schemas);
|
|
23996
23923
|
const editorSchema = {};
|
|
23997
23924
|
const tables = [];
|
|
23998
23925
|
schemas.forEach(([schemaName, schema]) => {
|
|
@@ -24001,8 +23928,8 @@
|
|
|
24001
23928
|
const quotedQualifiedTableName = schemas.length === 1 ? `\`${tableName}\`` : `\`${schemaName}\`.\`${tableName}\``;
|
|
24002
23929
|
const columns = Object.keys(table.columns);
|
|
24003
23930
|
editorSchema[qualifiedTableName] = columns;
|
|
24004
|
-
const alias =
|
|
24005
|
-
const boost =
|
|
23931
|
+
const alias = metadata.tables[qualifiedTableName]?.alias;
|
|
23932
|
+
const boost = metadata.tables[qualifiedTableName]?.boost;
|
|
24006
23933
|
if (alias) {
|
|
24007
23934
|
editorSchema[alias] = columns;
|
|
24008
23935
|
tables.push({
|
|
@@ -24067,7 +23994,7 @@
|
|
|
24067
23994
|
const originalSchemaCompletionSource = schemaCompletionSource(sqlConfig);
|
|
24068
23995
|
const originalKeywordCompletionSource = keywordCompletionSource(MySQL, true);
|
|
24069
23996
|
const keywordCompletions = [];
|
|
24070
|
-
|
|
23997
|
+
metadata.joins.forEach((join) => {
|
|
24071
23998
|
['JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'CROSS JOIN'].forEach((type) => {
|
|
24072
23999
|
keywordCompletions.push({ label: `${type} ${join.label}`, apply: `${type} ${join.apply}`, type: 'keyword' });
|
|
24073
24000
|
});
|
|
@@ -24166,7 +24093,7 @@
|
|
|
24166
24093
|
if (foundSchema) {
|
|
24167
24094
|
const unquotedLabel = unquoteSqlId(option.label);
|
|
24168
24095
|
const quoted = unquotedLabel !== option.label;
|
|
24169
|
-
const tableConfig =
|
|
24096
|
+
const tableConfig = metadata.tables[`${foundSchema}.${unquotedLabel}`];
|
|
24170
24097
|
const alias = tableConfig?.alias;
|
|
24171
24098
|
const boost = tableConfig?.boost || -1;
|
|
24172
24099
|
const optionOverride = {
|
|
@@ -24190,7 +24117,7 @@
|
|
|
24190
24117
|
})
|
|
24191
24118
|
]
|
|
24192
24119
|
);
|
|
24193
|
-
|
|
24120
|
+
return new EditorView({
|
|
24194
24121
|
state: EditorState.create({
|
|
24195
24122
|
extensions: [
|
|
24196
24123
|
lineNumbers(),
|
|
@@ -24217,7 +24144,149 @@
|
|
|
24217
24144
|
]
|
|
24218
24145
|
}),
|
|
24219
24146
|
parent
|
|
24147
|
+
})
|
|
24148
|
+
}
|
|
24149
|
+
|
|
24150
|
+
function unquoteSqlId (identifier) {
|
|
24151
|
+
const match = identifier.match(/^`(.*)`$/);
|
|
24152
|
+
return match ? match[1] : identifier
|
|
24153
|
+
}
|
|
24154
|
+
|
|
24155
|
+
function createTable (columns, rows, id, cellRenderer) {
|
|
24156
|
+
const tableElement = document.createElement('table');
|
|
24157
|
+
if (id) tableElement.id = id;
|
|
24158
|
+
const theadElement = document.createElement('thead');
|
|
24159
|
+
const headerTrElement = document.createElement('tr');
|
|
24160
|
+
const tbodyElement = document.createElement('tbody');
|
|
24161
|
+
theadElement.appendChild(headerTrElement);
|
|
24162
|
+
tableElement.appendChild(theadElement);
|
|
24163
|
+
tableElement.appendChild(tbodyElement);
|
|
24164
|
+
|
|
24165
|
+
columns.forEach(function (columnName) {
|
|
24166
|
+
const headerElement = document.createElement('th');
|
|
24167
|
+
headerElement.innerText = columnName;
|
|
24168
|
+
headerTrElement.appendChild(headerElement);
|
|
24169
|
+
});
|
|
24170
|
+
if (columns.length > 0) {
|
|
24171
|
+
headerTrElement.appendChild(document.createElement('th'));
|
|
24172
|
+
}
|
|
24173
|
+
let highlight = false;
|
|
24174
|
+
rows.forEach(function (row) {
|
|
24175
|
+
const rowElement = document.createElement('tr');
|
|
24176
|
+
if (highlight) {
|
|
24177
|
+
rowElement.classList.add('highlighted-row');
|
|
24178
|
+
}
|
|
24179
|
+
highlight = !highlight;
|
|
24180
|
+
tbodyElement.appendChild(rowElement);
|
|
24181
|
+
row.forEach(function (value, i) {
|
|
24182
|
+
let cellElement;
|
|
24183
|
+
if (cellRenderer) {
|
|
24184
|
+
cellElement = cellRenderer(columns[i], value);
|
|
24185
|
+
} else {
|
|
24186
|
+
cellElement = document.createElement('td');
|
|
24187
|
+
cellElement.innerText = value;
|
|
24188
|
+
}
|
|
24189
|
+
rowElement.appendChild(cellElement);
|
|
24190
|
+
});
|
|
24191
|
+
rowElement.appendChild(document.createElement('td'));
|
|
24192
|
+
});
|
|
24193
|
+
return tableElement
|
|
24194
|
+
}
|
|
24195
|
+
|
|
24196
|
+
/* global google */
|
|
24197
|
+
|
|
24198
|
+
function getSqlFromUrl (url) {
|
|
24199
|
+
const params = url.searchParams;
|
|
24200
|
+
if (params.has('file') && params.has('sql')) {
|
|
24201
|
+
// TODO: show an error.
|
|
24202
|
+
throw new Error('You can only specify a file or sql param, not both.')
|
|
24203
|
+
}
|
|
24204
|
+
if (params.has('sql')) {
|
|
24205
|
+
return params.get('sql')
|
|
24206
|
+
} else if (params.has('file')) {
|
|
24207
|
+
const file = params.get('file');
|
|
24208
|
+
const fileDetails = window.metadata.saved[file];
|
|
24209
|
+
if (!fileDetails) throw new Error(`no such file: ${file}`)
|
|
24210
|
+
return fileDetails.contents
|
|
24211
|
+
}
|
|
24212
|
+
throw new Error('You must specify a file or sql param')
|
|
24213
|
+
}
|
|
24214
|
+
|
|
24215
|
+
function init (parent, onSubmit, onShiftSubmit) {
|
|
24216
|
+
addClickListener(document.getElementById('query-tab-button'), (event) => selectTab(event, 'query'));
|
|
24217
|
+
addClickListener(document.getElementById('saved-tab-button'), (event) => selectTab(event, 'saved'));
|
|
24218
|
+
addClickListener(document.getElementById('structure-tab-button'), (event) => selectTab(event, 'structure'));
|
|
24219
|
+
addClickListener(document.getElementById('graph-tab-button'), (event) => selectTab(event, 'graph'));
|
|
24220
|
+
addClickListener(document.getElementById('cancel-button'), (event) => clearResult());
|
|
24221
|
+
|
|
24222
|
+
const dropdownContent = document.getElementById('submit-dropdown-content');
|
|
24223
|
+
const dropdownButton = document.getElementById('submit-dropdown-button');
|
|
24224
|
+
addClickListener(dropdownButton, () => dropdownContent.classList.toggle('submit-dropdown-content-show'));
|
|
24225
|
+
|
|
24226
|
+
const isMac = navigator.userAgent.includes('Mac');
|
|
24227
|
+
const runCurrentLabel = `run selection (${isMac ? '⌘' : 'Ctrl'}-Enter)`;
|
|
24228
|
+
const runAllLabel = `run all (${isMac ? '⌘' : 'Ctrl'}-Shift-Enter)`;
|
|
24229
|
+
|
|
24230
|
+
const submitButtonCurrent = document.getElementById('submit-button-current');
|
|
24231
|
+
submitButtonCurrent.value = runCurrentLabel;
|
|
24232
|
+
addClickListener(submitButtonCurrent, (event) => submitCurrent(event.target, event));
|
|
24233
|
+
|
|
24234
|
+
const submitButtonAll = document.getElementById('submit-button-all');
|
|
24235
|
+
submitButtonAll.value = runAllLabel;
|
|
24236
|
+
addClickListener(submitButtonAll, (event) => submitAll(event.target, event));
|
|
24237
|
+
|
|
24238
|
+
const dropdownButtonCurrent = document.getElementById('submit-dropdown-button-current');
|
|
24239
|
+
dropdownButtonCurrent.value = runCurrentLabel;
|
|
24240
|
+
addClickListener(dropdownButtonCurrent, (event) => submitCurrent(event.target, event));
|
|
24241
|
+
|
|
24242
|
+
const dropdownAllButton = document.getElementById('submit-dropdown-button-all');
|
|
24243
|
+
dropdownAllButton.value = runAllLabel;
|
|
24244
|
+
addClickListener(dropdownAllButton, (event) => submitAll(event.target, event));
|
|
24245
|
+
|
|
24246
|
+
const dropdownToggleButton = document.getElementById('submit-dropdown-button-toggle');
|
|
24247
|
+
addClickListener(dropdownToggleButton, () => {
|
|
24248
|
+
submitButtonCurrent.classList.toggle('submit-button-show');
|
|
24249
|
+
submitButtonAll.classList.toggle('submit-button-show');
|
|
24250
|
+
focus(getSelection());
|
|
24251
|
+
});
|
|
24252
|
+
|
|
24253
|
+
addClickListener(document.getElementById('submit-dropdown-button-copy-csv'), (event) => {
|
|
24254
|
+
if (window.sqlFetch?.result) {
|
|
24255
|
+
copyTextToClipboard(toCsv(window.sqlFetch.result.columns, window.sqlFetch.result.rows));
|
|
24256
|
+
}
|
|
24257
|
+
});
|
|
24258
|
+
addClickListener(document.getElementById('submit-dropdown-button-copy-tsv'), (event) => {
|
|
24259
|
+
if (window.sqlFetch?.result) {
|
|
24260
|
+
copyTextToClipboard(toTsv(window.sqlFetch.result.columns, window.sqlFetch.result.rows));
|
|
24261
|
+
}
|
|
24262
|
+
});
|
|
24263
|
+
addClickListener(document.getElementById('submit-dropdown-button-download-csv'), () => {
|
|
24264
|
+
if (!window.sqlFetch?.result) return
|
|
24265
|
+
|
|
24266
|
+
const url = new URL(window.location);
|
|
24267
|
+
url.searchParams.set('sql', base64Encode(getSqlFromUrl(url)));
|
|
24268
|
+
url.searchParams.delete('file');
|
|
24269
|
+
setActionInUrl(url, 'download_csv');
|
|
24270
|
+
|
|
24271
|
+
const link = document.createElement('a');
|
|
24272
|
+
link.setAttribute('download', 'result.csv');
|
|
24273
|
+
link.setAttribute('href', url.href);
|
|
24274
|
+
link.click();
|
|
24275
|
+
|
|
24276
|
+
focus(getSelection());
|
|
24277
|
+
});
|
|
24278
|
+
|
|
24279
|
+
document.addEventListener('click', function (event) {
|
|
24280
|
+
if (event.target !== dropdownButton) {
|
|
24281
|
+
dropdownContent.classList.remove('submit-dropdown-content-show');
|
|
24282
|
+
}
|
|
24283
|
+
});
|
|
24284
|
+
dropdownContent.addEventListener('focusout', function (event) {
|
|
24285
|
+
if (!dropdownContent.contains(event.relatedTarget)) {
|
|
24286
|
+
dropdownContent.classList.remove('submit-dropdown-content-show');
|
|
24287
|
+
}
|
|
24220
24288
|
});
|
|
24289
|
+
window.editorView = createEditor(parent, window.metadata, onSubmit, onShiftSubmit);
|
|
24221
24290
|
}
|
|
24222
24291
|
|
|
24223
24292
|
function addClickListener (element, func) {
|
|
@@ -24438,7 +24507,7 @@
|
|
|
24438
24507
|
}
|
|
24439
24508
|
rows.push(row);
|
|
24440
24509
|
}
|
|
24441
|
-
createTable(
|
|
24510
|
+
columnsElement.appendChild(createTable(columns, rows, null));
|
|
24442
24511
|
}
|
|
24443
24512
|
|
|
24444
24513
|
const indexEntries = Object.entries(table.indexes);
|
|
@@ -24458,49 +24527,12 @@
|
|
|
24458
24527
|
rows.push(row);
|
|
24459
24528
|
}
|
|
24460
24529
|
}
|
|
24461
|
-
createTable(
|
|
24530
|
+
indexesElement.appendChild(createTable(columns, rows, null));
|
|
24462
24531
|
}
|
|
24463
24532
|
});
|
|
24464
24533
|
window.structureLoaded = true;
|
|
24465
24534
|
}
|
|
24466
24535
|
|
|
24467
|
-
function createTable (parent, columns, rows) {
|
|
24468
|
-
const tableElement = document.createElement('table');
|
|
24469
|
-
const theadElement = document.createElement('thead');
|
|
24470
|
-
const headerTrElement = document.createElement('tr');
|
|
24471
|
-
const tbodyElement = document.createElement('tbody');
|
|
24472
|
-
theadElement.appendChild(headerTrElement);
|
|
24473
|
-
tableElement.appendChild(theadElement);
|
|
24474
|
-
tableElement.appendChild(tbodyElement);
|
|
24475
|
-
parent.appendChild(tableElement);
|
|
24476
|
-
|
|
24477
|
-
columns.forEach(function (columnName) {
|
|
24478
|
-
const headerElement = document.createElement('th');
|
|
24479
|
-
headerElement.classList.add('cell');
|
|
24480
|
-
headerElement.innerText = columnName;
|
|
24481
|
-
headerTrElement.appendChild(headerElement);
|
|
24482
|
-
});
|
|
24483
|
-
if (columns.length > 0) {
|
|
24484
|
-
headerTrElement.appendChild(document.createElement('th'));
|
|
24485
|
-
}
|
|
24486
|
-
let highlight = false;
|
|
24487
|
-
rows.forEach(function (row) {
|
|
24488
|
-
const rowElement = document.createElement('tr');
|
|
24489
|
-
if (highlight) {
|
|
24490
|
-
rowElement.classList.add('highlighted-row');
|
|
24491
|
-
}
|
|
24492
|
-
highlight = !highlight;
|
|
24493
|
-
tbodyElement.appendChild(rowElement);
|
|
24494
|
-
row.forEach(function (value) {
|
|
24495
|
-
const cellElement = document.createElement('td');
|
|
24496
|
-
cellElement.classList.add('cell');
|
|
24497
|
-
cellElement.innerText = value;
|
|
24498
|
-
rowElement.appendChild(cellElement);
|
|
24499
|
-
});
|
|
24500
|
-
rowElement.appendChild(document.createElement('td'));
|
|
24501
|
-
});
|
|
24502
|
-
}
|
|
24503
|
-
|
|
24504
24536
|
function selectGraphTab (internal) {
|
|
24505
24537
|
document.getElementById('query-box').style.display = 'flex';
|
|
24506
24538
|
document.getElementById('submit-box').style.display = 'flex';
|
|
@@ -24892,39 +24924,32 @@
|
|
|
24892
24924
|
clearResultBox();
|
|
24893
24925
|
displaySqlFetchResultStatus('result-status', fetch);
|
|
24894
24926
|
|
|
24895
|
-
const
|
|
24896
|
-
|
|
24897
|
-
|
|
24898
|
-
|
|
24899
|
-
|
|
24900
|
-
theadElement.appendChild(headerElement);
|
|
24901
|
-
tableElement.appendChild(theadElement);
|
|
24902
|
-
tableElement.appendChild(tbodyElement);
|
|
24903
|
-
document.getElementById('result-box').appendChild(tableElement);
|
|
24927
|
+
const createLink = function (link, value) {
|
|
24928
|
+
const linkElement = document.createElement('a');
|
|
24929
|
+
linkElement.href = link.template.replaceAll('{*}', encodeURIComponent(value));
|
|
24930
|
+
linkElement.innerText = link.short_name;
|
|
24931
|
+
linkElement.target = '_blank';
|
|
24904
24932
|
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
24908
|
-
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
|
|
24912
|
-
|
|
24913
|
-
|
|
24914
|
-
|
|
24915
|
-
|
|
24916
|
-
|
|
24917
|
-
|
|
24933
|
+
const abbrElement = document.createElement('abbr');
|
|
24934
|
+
abbrElement.title = link.long_name;
|
|
24935
|
+
abbrElement.appendChild(linkElement);
|
|
24936
|
+
|
|
24937
|
+
return abbrElement
|
|
24938
|
+
};
|
|
24939
|
+
const cellRenderer = function (column, value) {
|
|
24940
|
+
const cellElement = document.createElement('td');
|
|
24941
|
+
if (window.metadata.columns[column]?.links?.length > 0) {
|
|
24942
|
+
cellElement.appendChild(document.createTextNode(value));
|
|
24943
|
+
window.metadata.columns[column].links.forEach((link) => {
|
|
24944
|
+
cellElement.appendChild(createLink(link, value));
|
|
24945
|
+
});
|
|
24946
|
+
} else {
|
|
24947
|
+
cellElement.innerText = value;
|
|
24918
24948
|
}
|
|
24919
|
-
|
|
24920
|
-
|
|
24921
|
-
|
|
24922
|
-
|
|
24923
|
-
template.innerHTML = `<td class="cell">${value}</td>`;
|
|
24924
|
-
rowElement.appendChild(template.content.firstChild);
|
|
24925
|
-
});
|
|
24926
|
-
rowElement.appendChild(document.createElement('td'));
|
|
24927
|
-
});
|
|
24949
|
+
return cellElement
|
|
24950
|
+
};
|
|
24951
|
+
document.getElementById('result-box')
|
|
24952
|
+
.appendChild(createTable(fetch.result.columns, fetch.result.rows, 'result-table', cellRenderer));
|
|
24928
24953
|
}
|
|
24929
24954
|
|
|
24930
24955
|
function disableDownloadButtons () {
|
|
@@ -25116,10 +25141,12 @@
|
|
|
25116
25141
|
if (contentType && contentType.indexOf('application/json') !== -1) {
|
|
25117
25142
|
return response.json().then((result) => {
|
|
25118
25143
|
if (result.error) {
|
|
25119
|
-
let error =
|
|
25144
|
+
let error = '<div style="font-family: monospace; font-size: 16px;">\n';
|
|
25145
|
+
error += `<div>${result.error}</div>\n`;
|
|
25120
25146
|
if (result.stacktrace) {
|
|
25121
|
-
error += '
|
|
25147
|
+
error += '<pre>\n' + result.stacktrace + '\n</pre>\n';
|
|
25122
25148
|
}
|
|
25149
|
+
error += '</div>\n';
|
|
25123
25150
|
document.getElementById('loading-box').innerHTML = error;
|
|
25124
25151
|
} else if (!result.server) {
|
|
25125
25152
|
document.getElementById('loading-box').innerHTML = `
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sqlui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.48
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Dower
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-11-
|
|
11
|
+
date: 2022-11-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mysql2
|
|
@@ -140,7 +140,9 @@ files:
|
|
|
140
140
|
- app/sqlui.rb
|
|
141
141
|
- app/sqlui_config.rb
|
|
142
142
|
- app/views/databases.erb
|
|
143
|
+
- app/views/error.erb
|
|
143
144
|
- bin/sqlui
|
|
145
|
+
- client/resources/favicon.svg
|
|
144
146
|
- client/resources/sqlui.css
|
|
145
147
|
- client/resources/sqlui.html
|
|
146
148
|
- client/resources/sqlui.js
|