sqlui 0.1.17 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
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.17
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-22 00:00:00.000000000 Z
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: sinatra
126
+ name: selenium-webdriver
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '3.0'
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: '3.0'
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/sqlui.rb
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