sqlui 0.1.16 → 0.1.18
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/environment.rb +4 -1
- data/app/server.rb +22 -18
- data/app/sqlui.rb +37 -38
- data/app/views/databases.erb +10 -9
- data/bin/sqlui +1 -0
- data/client/resources/sqlui.js +383 -371
- metadata +22 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f951e672b477e0fd4882c1aa4e42472975ac07fa6a2955eb4430bc1018ea6165
|
4
|
+
data.tar.gz: d8e7da6fe289ed3e94322a3ea3d5ce461356cbfa73e2e2732bab9001a8d3af24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6769e3ee3c0b5330ca0b1ac1433370756838cec3187f0bd9822308704d31ee1608592199acb9b622edba8d45146c557e413d42c0a060bb9ace9b7653b43ad54c
|
7
|
+
data.tar.gz: 4ef70417125cdb931c5f6edba2ef7986be7bda22f0f27c362d7bea97df81752133493e9507b941cbfe51261fa4e77aca85f4838354805a97d88e64f0240e452d
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.18
|
data/app/environment.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Parses and provides access to environment variables.
|
1
4
|
class Environment
|
2
5
|
SERVER_ENV = ENV.fetch('SERVER_ENV', 'development').to_sym
|
3
6
|
SERVER_PORT = ENV.fetch('SERVER_PORT', 8080)
|
@@ -17,4 +20,4 @@ class Environment
|
|
17
20
|
def self.server_port
|
18
21
|
SERVER_PORT
|
19
22
|
end
|
20
|
-
end
|
23
|
+
end
|
data/app/server.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
require 'json'
|
3
5
|
require 'mysql2'
|
@@ -12,14 +14,16 @@ if ARGV.include?('-v') || ARGV.include?('--version')
|
|
12
14
|
end
|
13
15
|
|
14
16
|
raise 'you must specify a configuration file' unless ARGV.size == 1
|
15
|
-
raise
|
17
|
+
raise 'configuration file does not exist' unless File.exist?(ARGV[0])
|
16
18
|
|
19
|
+
# SQLUI Sinatra server.
|
17
20
|
class Server < Sinatra::Base
|
18
21
|
set :logging, true
|
19
22
|
set :bind, '0.0.0.0'
|
20
23
|
set :port, Environment.server_port
|
21
24
|
set :env, Environment.server_env
|
22
25
|
|
26
|
+
# A MySQL client. This needs to go away.
|
23
27
|
class Client
|
24
28
|
def initialize(params)
|
25
29
|
@params = params
|
@@ -35,30 +39,30 @@ class Server < Sinatra::Base
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
38
|
-
config = YAML.
|
42
|
+
config = YAML.safe_load(ERB.new(File.read(ARGV[0])).result)
|
39
43
|
saved_path_root = config['saved_path']
|
40
|
-
client_map = config['databases'].values.
|
44
|
+
client_map = config['databases'].values.to_h do |database_config|
|
41
45
|
client_params = {
|
42
|
-
host:
|
43
|
-
port:
|
44
|
-
username:
|
45
|
-
password:
|
46
|
-
database:
|
47
|
-
read_timeout:
|
48
|
-
write_timeout:
|
46
|
+
host: database_config['db_host'],
|
47
|
+
port: database_config['db_port'] || 3306,
|
48
|
+
username: database_config['db_username'],
|
49
|
+
password: database_config['db_password'],
|
50
|
+
database: database_config['db_database'],
|
51
|
+
read_timeout: 10, # seconds
|
52
|
+
write_timeout: 0, # seconds
|
49
53
|
connect_timeout: 5 # seconds
|
50
54
|
}
|
51
55
|
client = Client.new(client_params)
|
52
56
|
[
|
53
|
-
database_config['url_path'],
|
57
|
+
database_config['url_path'],
|
54
58
|
::SQLUI.new(
|
55
|
-
client:
|
59
|
+
client: client,
|
56
60
|
table_schema: database_config['db_database'],
|
57
|
-
name:
|
58
|
-
saved_path:
|
61
|
+
name: database_config['name'],
|
62
|
+
saved_path: File.join(saved_path_root, database_config['saved_path'])
|
59
63
|
)
|
60
64
|
]
|
61
|
-
end
|
65
|
+
end
|
62
66
|
|
63
67
|
get '/-/health' do
|
64
68
|
status 200
|
@@ -66,7 +70,7 @@ class Server < Sinatra::Base
|
|
66
70
|
end
|
67
71
|
|
68
72
|
get '/db/?' do
|
69
|
-
erb :databases, :
|
73
|
+
erb :databases, locals: { databases: config['databases'] }
|
70
74
|
end
|
71
75
|
|
72
76
|
get '/db/:database' do
|
@@ -76,7 +80,7 @@ class Server < Sinatra::Base
|
|
76
80
|
get '/db/:database/:route' do
|
77
81
|
response = client_map[params[:database]].get(params)
|
78
82
|
status response[:status]
|
79
|
-
headers
|
83
|
+
headers 'Content-Type': response[:content_type]
|
80
84
|
body response[:body]
|
81
85
|
end
|
82
86
|
|
@@ -84,7 +88,7 @@ class Server < Sinatra::Base
|
|
84
88
|
post_body = JSON.parse(request.body.read)
|
85
89
|
response = client_map[params[:database]].post(params.merge(post_body))
|
86
90
|
status response[:status]
|
87
|
-
headers
|
91
|
+
headers 'Content-Type': response[:content_type]
|
88
92
|
body response[:body]
|
89
93
|
end
|
90
94
|
|
data/app/sqlui.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'uri'
|
3
5
|
require 'set'
|
4
6
|
|
7
|
+
# Main SQLUI class responsible for producing web content. This class needs to die.
|
5
8
|
class SQLUI
|
6
9
|
MAX_ROWS = 1_000
|
7
10
|
|
8
|
-
def initialize(client:, table_schema: nil,
|
11
|
+
def initialize(client:, name:, saved_path:, table_schema: nil, max_rows: MAX_ROWS)
|
9
12
|
@client = client
|
10
13
|
@table_schema = table_schema
|
11
14
|
@name = name
|
@@ -65,35 +68,33 @@ class SQLUI
|
|
65
68
|
end
|
66
69
|
|
67
70
|
def query_file(params)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
raise 'missing file param'
|
73
|
-
end
|
71
|
+
raise 'missing file param' unless params['file']
|
72
|
+
|
73
|
+
sql = File.read("#{@saved_path}/#{params['file']}")
|
74
|
+
execute_query(sql).tap { |r| r[:file] = params[:file] }
|
74
75
|
end
|
75
76
|
|
76
77
|
def metadata
|
77
|
-
|
78
|
+
load_metadata
|
78
79
|
end
|
79
80
|
|
80
81
|
def load_metadata
|
81
82
|
result = {
|
82
|
-
server:
|
83
|
+
server: @name,
|
83
84
|
schemas: {},
|
84
|
-
saved:
|
85
|
+
saved: Dir.glob("#{@saved_path}/*.sql").map do |path|
|
85
86
|
{
|
86
|
-
filename:
|
87
|
+
filename: File.basename(path),
|
87
88
|
description: File.readlines(path).take_while { |l| l.start_with?('--') }.map { |l| l.sub(/^-- */, '') }.join
|
88
89
|
}
|
89
90
|
end
|
90
91
|
}
|
91
92
|
|
92
|
-
if @table_schema
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
where_clause = if @table_schema
|
94
|
+
"where table_schema = '#{@table_schema}'"
|
95
|
+
else
|
96
|
+
"where table_schema not in('mysql', 'sys', 'information_schema', 'performance_schema')"
|
97
|
+
end
|
97
98
|
column_result = @client.query(
|
98
99
|
<<~SQL
|
99
100
|
select
|
@@ -129,9 +130,7 @@ class SQLUI
|
|
129
130
|
end
|
130
131
|
columns = result[:schemas][table_schema][:tables][table_name][:columns]
|
131
132
|
column_name = row[:column_name]
|
132
|
-
unless columns[column_name]
|
133
|
-
columns[column_name] = {}
|
134
|
-
end
|
133
|
+
columns[column_name] = {} unless columns[column_name]
|
135
134
|
column = columns[column_name]
|
136
135
|
column[:name] = column_name
|
137
136
|
column[:data_type] = row[:data_type]
|
@@ -142,11 +141,11 @@ class SQLUI
|
|
142
141
|
column[:extra] = row[:extra]
|
143
142
|
end
|
144
143
|
|
145
|
-
if @table_schema
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
144
|
+
where_clause = if @table_schema
|
145
|
+
"where table_schema = '#{@table_schema}'"
|
146
|
+
else
|
147
|
+
"where table_schema not in('mysql', 'sys', 'information_schema', 'performance_schema')"
|
148
|
+
end
|
150
149
|
stats_result = @client.query(
|
151
150
|
<<~SQL
|
152
151
|
select
|
@@ -168,9 +167,7 @@ class SQLUI
|
|
168
167
|
table_name = row[:table_name]
|
169
168
|
indexes = tables[table_name][:indexes]
|
170
169
|
index_name = row[:index_name]
|
171
|
-
unless indexes[index_name]
|
172
|
-
indexes[index_name] = {}
|
173
|
-
end
|
170
|
+
indexes[index_name] = {} unless indexes[index_name]
|
174
171
|
index = indexes[index_name]
|
175
172
|
column_name = row[:column_name]
|
176
173
|
index[column_name] = {}
|
@@ -186,13 +183,13 @@ class SQLUI
|
|
186
183
|
|
187
184
|
def execute_query(sql)
|
188
185
|
result = @client.query(sql)
|
189
|
-
rows = result.map
|
186
|
+
rows = result.map(&:values)
|
190
187
|
columns = result.first&.keys || []
|
191
188
|
column_types = columns.map { |_| 'string' }
|
192
189
|
unless rows.empty?
|
193
190
|
maybe_non_null_column_value_exemplars = columns.each_with_index.map do |_, index|
|
194
|
-
row = rows.find do |
|
195
|
-
!
|
191
|
+
row = rows.find do |current|
|
192
|
+
!current[index].nil?
|
196
193
|
end
|
197
194
|
row.nil? ? nil : row[index]
|
198
195
|
end
|
@@ -210,22 +207,24 @@ class SQLUI
|
|
210
207
|
end
|
211
208
|
end
|
212
209
|
{
|
213
|
-
query:
|
214
|
-
columns:
|
210
|
+
query: sql,
|
211
|
+
columns: columns,
|
215
212
|
column_types: column_types,
|
216
|
-
total_rows:
|
217
|
-
rows:
|
213
|
+
total_rows: rows.size,
|
214
|
+
rows: rows.take(@max_rows)
|
218
215
|
}
|
219
216
|
end
|
220
217
|
|
221
218
|
def find_query_at_cursor(sql, cursor)
|
222
219
|
parts_with_ranges = []
|
223
220
|
sql.scan(/[^;]*;[ \n]*/) { |part| parts_with_ranges << [part, 0, part.size] }
|
224
|
-
parts_with_ranges.inject(0) do |pos,
|
225
|
-
|
226
|
-
|
221
|
+
parts_with_ranges.inject(0) do |pos, current|
|
222
|
+
current[1] += pos
|
223
|
+
current[2] += pos
|
227
224
|
end
|
228
|
-
part_with_range = parts_with_ranges.find
|
225
|
+
part_with_range = parts_with_ranges.find do |current|
|
226
|
+
cursor >= current[1] && cursor < current[2]
|
227
|
+
end || parts_with_ranges[-1]
|
229
228
|
part_with_range[0]
|
230
229
|
end
|
231
230
|
end
|
data/app/views/databases.erb
CHANGED
@@ -5,11 +5,13 @@
|
|
5
5
|
<style>
|
6
6
|
body {
|
7
7
|
font-family: Helvetica;
|
8
|
+
margin: 0px;
|
8
9
|
}
|
9
10
|
|
10
11
|
h1 {
|
11
12
|
font-size: 30px;
|
12
|
-
margin
|
13
|
+
margin: 10px;
|
14
|
+
|
13
15
|
}
|
14
16
|
|
15
17
|
.database a {
|
@@ -20,8 +22,6 @@
|
|
20
22
|
|
21
23
|
.database h2 {
|
22
24
|
margin: 0px;
|
23
|
-
margin-top: 10px;
|
24
|
-
margin-bottom: 0px;
|
25
25
|
font-size: 20px;
|
26
26
|
font-weight: bold;
|
27
27
|
}
|
@@ -29,11 +29,12 @@
|
|
29
29
|
.database p {
|
30
30
|
margin: 0px;
|
31
31
|
margin-top: 10px;
|
32
|
-
padding-bottom: 20px;
|
33
32
|
font-size: 16px;
|
34
33
|
}
|
35
34
|
|
36
35
|
.database {
|
36
|
+
margin: 0px;
|
37
|
+
padding: 10px;
|
37
38
|
cursor: pointer;
|
38
39
|
border-bottom: 1px solid #eeeeee;
|
39
40
|
}
|
@@ -52,11 +53,11 @@
|
|
52
53
|
<h1>Databases</h1>
|
53
54
|
<% databases.values.each do |database| %>
|
54
55
|
<div class="database" onclick="window.location='<%= "/db/#{database['url_path']}/app" %>'">
|
55
|
-
<h2><%= database['name'] %></h2>
|
56
|
-
<a href="/db/<%= database['url_path'] %>/app">query</a>
|
57
|
-
<a href="/db/<%= database['url_path'] %>/app?tab=saved">saved</a>
|
58
|
-
<a href="/db/<%= database['url_path'] %>/app?tab=structure">structure</a>
|
59
|
-
<p>
|
56
|
+
<h2 class='name'><%= database['name'] %></h2>
|
57
|
+
<a class='query-link' href="/db/<%= database['url_path'] %>/app">query</a>
|
58
|
+
<a class='saved-link' href="/db/<%= database['url_path'] %>/app?tab=saved">saved</a>
|
59
|
+
<a class='structure-link' href="/db/<%= database['url_path'] %>/app?tab=structure">structure</a>
|
60
|
+
<p class='description'>
|
60
61
|
<%= database['description'] %>
|
61
62
|
</p>
|
62
63
|
</div>
|
data/bin/sqlui
CHANGED