sqlui 0.1.18 → 0.1.20

Sign up to get free protection for your applications and to get access to all the features.
data/app/sqlui.rb CHANGED
@@ -1,230 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'uri'
5
- require 'set'
3
+ require_relative 'sqlui_config'
4
+ require_relative 'server'
6
5
 
7
- # Main SQLUI class responsible for producing web content. This class needs to die.
8
- class SQLUI
6
+ # Main entry point.
7
+ class Sqlui
9
8
  MAX_ROWS = 1_000
10
9
 
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]
10
+ def initialize(config_file)
11
+ raise 'you must specify a configuration file' unless config_file
12
+ raise 'configuration file does not exist' unless File.exist?(config_file)
63
13
 
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']
14
+ @config = SqluiConfig.new(config_file)
15
+ @resources_dir = File.join(File.expand_path('..', File.dirname(__FILE__)), 'client', 'resources')
72
16
 
73
- sql = File.read("#{@saved_path}/#{params['file']}")
74
- execute_query(sql).tap { |r| r[:file] = params[:file] }
17
+ # Connect to each database to verify each can be connected to.
18
+ @config.database_configs.each { |database| database.with_client { |client| client } }
75
19
  end
76
20
 
77
- def metadata
78
- load_metadata
21
+ def run
22
+ Server.init_and_run(@config, @resources_dir)
79
23
  end
80
24
 
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]
25
+ def self.from_command_line(args)
26
+ if args.include?('-v') || args.include?('--version')
27
+ puts File.read('.version')
28
+ exit
142
29
  end
143
30
 
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
31
+ raise 'you must specify a configuration file' unless args.size == 1
32
+ raise 'configuration file does not exist' unless File.exist?(args[0])
183
33
 
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]
34
+ Sqlui.new(args[0])
229
35
  end
230
36
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require_relative 'database_config'
5
+ require_relative 'args'
6
+
7
+ # App config including database configs.
8
+ class SqluiConfig
9
+ attr_reader :name, :list_url_path, :database_configs
10
+
11
+ def initialize(filename)
12
+ config = YAML.safe_load(ERB.new(File.read(filename)).result)
13
+ deep_symbolize!(config)
14
+ @name = Args.fetch_non_empty_string(config, :name).strip
15
+ @list_url_path = Args.fetch_non_empty_string(config, :list_url_path).strip
16
+ raise ArgumentError, 'list_url_path should start with a /' unless @list_url_path.start_with?('/')
17
+ if @list_url_path.length > 1 && @list_url_path.end_with?('/')
18
+ raise ArgumentError, 'list_url_path should not end with a /'
19
+ end
20
+
21
+ databases = Args.fetch_non_empty_hash(config, :databases)
22
+ @database_configs = databases.map do |_, current|
23
+ DatabaseConfig.new(current)
24
+ end
25
+ end
26
+
27
+ def database_config_for(url_path:)
28
+ config = @database_configs.find { |database| database.url_path == url_path }
29
+ raise ArgumentError, "no config found for path #{url_path}" unless config
30
+
31
+ config
32
+ end
33
+
34
+ private
35
+
36
+ def deep_symbolize!(object)
37
+ return object unless object.is_a? Hash
38
+
39
+ object.transform_keys!(&:to_sym)
40
+ object.each_value { |child| deep_symbolize!(child) }
41
+
42
+ object
43
+ end
44
+ end
@@ -50,15 +50,15 @@
50
50
  </head>
51
51
 
52
52
  <body>
53
- <h1>Databases</h1>
54
- <% databases.values.each do |database| %>
55
- <div class="database" onclick="window.location='<%= "/db/#{database['url_path']}/app" %>'">
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>
53
+ <h1><% config.name %> Databases</h1>
54
+ <% config.database_configs.each do |database_config| %>
55
+ <div class="database" onclick="window.location='<%= "#{database_config.url_path}/app" %>'">
56
+ <h2 class='name'><%= database_config.display_name %></h2>
57
+ <a class='query-link' href="<%= database_config.url_path %>/app">query</a>
58
+ <a class='saved-link' href="<%= database_config.url_path %>/app?tab=saved">saved</a>
59
+ <a class='structure-link' href="<%= database_config.url_path %>/app?tab=structure">structure</a>
60
60
  <p class='description'>
61
- <%= database['description'] %>
61
+ <%= database_config.description %>
62
62
  </p>
63
63
  </div>
64
64
  <% end %>
data/bin/sqlui CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative '../app/server'
4
+ require_relative '../app/sqlui'
5
+
6
+ Sqlui.from_command_line(ARGV).run