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.
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