sqlui 0.1.17 → 0.1.19
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 +30 -0
- data/app/server.rb +228 -57
- data/app/sql_parser.rb +17 -0
- data/app/sqlui_config.rb +49 -0
- data/app/views/databases.erb +14 -13
- data/client/resources/sqlui.js +262 -257
- metadata +8 -6
- data/app/sqlui.rb +0 -230
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.19
|
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-10-
|
11
|
+
date: 2022-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mysql2
|
@@ -123,19 +123,19 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: selenium-webdriver
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '4.0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '4.0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: watir
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,9 +158,11 @@ extensions: []
|
|
158
158
|
extra_rdoc_files: []
|
159
159
|
files:
|
160
160
|
- ".version"
|
161
|
+
- app/database_config.rb
|
161
162
|
- app/environment.rb
|
162
163
|
- app/server.rb
|
163
|
-
- app/
|
164
|
+
- app/sql_parser.rb
|
165
|
+
- app/sqlui_config.rb
|
164
166
|
- app/views/databases.erb
|
165
167
|
- bin/sqlui
|
166
168
|
- client/resources/sqlui.css
|
data/app/sqlui.rb
DELETED
@@ -1,230 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'uri'
|
5
|
-
require 'set'
|
6
|
-
|
7
|
-
# Main SQLUI class responsible for producing web content. This class needs to die.
|
8
|
-
class SQLUI
|
9
|
-
MAX_ROWS = 1_000
|
10
|
-
|
11
|
-
def initialize(client:, name:, saved_path:, table_schema: nil, max_rows: MAX_ROWS)
|
12
|
-
@client = client
|
13
|
-
@table_schema = table_schema
|
14
|
-
@name = name
|
15
|
-
@saved_path = saved_path
|
16
|
-
@max_rows = max_rows
|
17
|
-
@resources_dir = File.join(File.expand_path('..', File.dirname(__FILE__)), 'client', 'resources')
|
18
|
-
end
|
19
|
-
|
20
|
-
def get(params)
|
21
|
-
case params[:route]
|
22
|
-
when 'app'
|
23
|
-
{ body: html, status: 200, content_type: 'text/html' }
|
24
|
-
when 'sqlui.css'
|
25
|
-
{ body: css, status: 200, content_type: 'text/css' }
|
26
|
-
when 'sqlui.js'
|
27
|
-
{ body: javascript, status: 200, content_type: 'text/javascript' }
|
28
|
-
when 'metadata'
|
29
|
-
{ body: metadata.to_json, status: 200, content_type: 'application/json' }
|
30
|
-
when 'query_file'
|
31
|
-
{ body: query_file(params).to_json, status: 200, content_type: 'application/json' }
|
32
|
-
else
|
33
|
-
{ body: "unknown route: #{params[:route]}", status: 404, content_type: 'text/plain' }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def post(params)
|
38
|
-
case params[:route]
|
39
|
-
when 'query'
|
40
|
-
{ body: query(params).to_json, status: 200, content_type: 'application/json' }
|
41
|
-
else
|
42
|
-
{ body: "unknown route: #{params[:route]}", status: 404, content_type: 'text/plain' }
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def html
|
49
|
-
@html ||= File.read(File.join(@resources_dir, 'sqlui.html'))
|
50
|
-
end
|
51
|
-
|
52
|
-
def css
|
53
|
-
@css ||= File.read(File.join(@resources_dir, 'sqlui.css'))
|
54
|
-
end
|
55
|
-
|
56
|
-
def javascript
|
57
|
-
@javascript ||= File.read(File.join(@resources_dir, 'sqlui.js'))
|
58
|
-
end
|
59
|
-
|
60
|
-
def query(params)
|
61
|
-
raise 'missing sql' unless params[:sql]
|
62
|
-
raise 'missing cursor' unless params[:cursor]
|
63
|
-
|
64
|
-
sql = find_query_at_cursor(params[:sql], Integer(params[:cursor]))
|
65
|
-
raise "can't find query at cursor" unless sql
|
66
|
-
|
67
|
-
execute_query(sql)
|
68
|
-
end
|
69
|
-
|
70
|
-
def query_file(params)
|
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] }
|
75
|
-
end
|
76
|
-
|
77
|
-
def metadata
|
78
|
-
@metadata ||= load_metadata
|
79
|
-
end
|
80
|
-
|
81
|
-
def load_metadata
|
82
|
-
result = {
|
83
|
-
server: @name,
|
84
|
-
schemas: {},
|
85
|
-
saved: Dir.glob("#{@saved_path}/*.sql").map do |path|
|
86
|
-
{
|
87
|
-
filename: File.basename(path),
|
88
|
-
description: File.readlines(path).take_while { |l| l.start_with?('--') }.map { |l| l.sub(/^-- */, '') }.join
|
89
|
-
}
|
90
|
-
end
|
91
|
-
}
|
92
|
-
|
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
|
98
|
-
column_result = @client.query(
|
99
|
-
<<~SQL
|
100
|
-
select
|
101
|
-
table_schema,
|
102
|
-
table_name,
|
103
|
-
column_name,
|
104
|
-
data_type,
|
105
|
-
character_maximum_length,
|
106
|
-
is_nullable,
|
107
|
-
column_key,
|
108
|
-
column_default,
|
109
|
-
extra
|
110
|
-
from information_schema.columns
|
111
|
-
#{where_clause}
|
112
|
-
order by table_schema, table_name, column_name, ordinal_position;
|
113
|
-
SQL
|
114
|
-
)
|
115
|
-
column_result.each do |row|
|
116
|
-
row = row.transform_keys(&:downcase).transform_keys(&:to_sym)
|
117
|
-
table_schema = row[:table_schema]
|
118
|
-
unless result[:schemas][table_schema]
|
119
|
-
result[:schemas][table_schema] = {
|
120
|
-
tables: {}
|
121
|
-
}
|
122
|
-
end
|
123
|
-
table_name = row[:table_name]
|
124
|
-
tables = result[:schemas][table_schema][:tables]
|
125
|
-
unless tables[table_name]
|
126
|
-
tables[table_name] = {
|
127
|
-
indexes: {},
|
128
|
-
columns: {}
|
129
|
-
}
|
130
|
-
end
|
131
|
-
columns = result[:schemas][table_schema][:tables][table_name][:columns]
|
132
|
-
column_name = row[:column_name]
|
133
|
-
columns[column_name] = {} unless columns[column_name]
|
134
|
-
column = columns[column_name]
|
135
|
-
column[:name] = column_name
|
136
|
-
column[:data_type] = row[:data_type]
|
137
|
-
column[:length] = row[:character_maximum_length]
|
138
|
-
column[:allow_null] = row[:is_nullable]
|
139
|
-
column[:key] = row[:column_key]
|
140
|
-
column[:default] = row[:column_default]
|
141
|
-
column[:extra] = row[:extra]
|
142
|
-
end
|
143
|
-
|
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
|
149
|
-
stats_result = @client.query(
|
150
|
-
<<~SQL
|
151
|
-
select
|
152
|
-
table_schema,
|
153
|
-
table_name,
|
154
|
-
index_name,
|
155
|
-
seq_in_index,
|
156
|
-
non_unique,
|
157
|
-
column_name
|
158
|
-
from information_schema.statistics
|
159
|
-
#{where_clause}
|
160
|
-
order by table_schema, table_name, if(index_name = "PRIMARY", 0, index_name), seq_in_index;
|
161
|
-
SQL
|
162
|
-
)
|
163
|
-
stats_result.each do |row|
|
164
|
-
row = row.transform_keys(&:downcase).transform_keys(&:to_sym)
|
165
|
-
table_schema = row[:table_schema]
|
166
|
-
tables = result[:schemas][table_schema][:tables]
|
167
|
-
table_name = row[:table_name]
|
168
|
-
indexes = tables[table_name][:indexes]
|
169
|
-
index_name = row[:index_name]
|
170
|
-
indexes[index_name] = {} unless indexes[index_name]
|
171
|
-
index = indexes[index_name]
|
172
|
-
column_name = row[:column_name]
|
173
|
-
index[column_name] = {}
|
174
|
-
column = index[column_name]
|
175
|
-
column[:name] = index_name
|
176
|
-
column[:seq_in_index] = row[:seq_in_index]
|
177
|
-
column[:non_unique] = row[:non_unique]
|
178
|
-
column[:column_name] = row[:column_name]
|
179
|
-
end
|
180
|
-
|
181
|
-
result
|
182
|
-
end
|
183
|
-
|
184
|
-
def execute_query(sql)
|
185
|
-
result = @client.query(sql)
|
186
|
-
rows = result.map(&:values)
|
187
|
-
columns = result.first&.keys || []
|
188
|
-
column_types = columns.map { |_| 'string' }
|
189
|
-
unless rows.empty?
|
190
|
-
maybe_non_null_column_value_exemplars = columns.each_with_index.map do |_, index|
|
191
|
-
row = rows.find do |current|
|
192
|
-
!current[index].nil?
|
193
|
-
end
|
194
|
-
row.nil? ? nil : row[index]
|
195
|
-
end
|
196
|
-
column_types = maybe_non_null_column_value_exemplars.map do |value|
|
197
|
-
case value
|
198
|
-
when String, NilClass
|
199
|
-
'string'
|
200
|
-
when Integer
|
201
|
-
'number'
|
202
|
-
when Date, Time
|
203
|
-
'date'
|
204
|
-
else
|
205
|
-
value.class.to_s
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
{
|
210
|
-
query: sql,
|
211
|
-
columns: columns,
|
212
|
-
column_types: column_types,
|
213
|
-
total_rows: rows.size,
|
214
|
-
rows: rows.take(@max_rows)
|
215
|
-
}
|
216
|
-
end
|
217
|
-
|
218
|
-
def find_query_at_cursor(sql, cursor)
|
219
|
-
parts_with_ranges = []
|
220
|
-
sql.scan(/[^;]*;[ \n]*/) { |part| parts_with_ranges << [part, 0, part.size] }
|
221
|
-
parts_with_ranges.inject(0) do |pos, current|
|
222
|
-
current[1] += pos
|
223
|
-
current[2] += pos
|
224
|
-
end
|
225
|
-
part_with_range = parts_with_ranges.find do |current|
|
226
|
-
cursor >= current[1] && cursor < current[2]
|
227
|
-
end || parts_with_ranges[-1]
|
228
|
-
part_with_range[0]
|
229
|
-
end
|
230
|
-
end
|