sqlui 0.1.16 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- 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