Class: Strata::CLI::SubCommands::Datasource

Inherits:
Thor
  • Object
show all
Extended by:
Helpers::DescriptionHelper
Includes:
DatasourceHelper, Guard, Terminal, Thor::Actions
Defined in:
lib/strata/cli/sub_commands/datasource.rb

Constant Summary

Constants included from DatasourceHelper

DatasourceHelper::ADAPTER_DRIVER_GEMS

Constants included from Guard

Guard::ALLOWED_COMMANDS

Instance Method Summary collapse

Methods included from Helpers::DescriptionHelper

long_desc_from_file

Methods included from DatasourceHelper

#apply_readonly_mode, #create_adapter, #ds_config, #ensure_adapter_driver_gems!, #load_adapter_driver_gems!, #resolve_datasource, #resolve_datasource_value

Methods included from Terminal

#create_spinner, #print_table, #with_spinner

Methods included from Guard

#invoke_command

Instance Method Details

#adaptersObject



22
23
24
25
26
27
# File 'lib/strata/cli/sub_commands/datasource.rb', line 22

def adapters
  say "\n\tSupported Adapters\n\n", :yellow
  DWH.adapters.each_key do
    say "\t\t● #{it}", :magenta
  end
end

#add(adapter_name = nil) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/strata/cli/sub_commands/datasource.rb', line 60

def add(adapter_name = nil)
  prompt = TTY::Prompt.new

  if adapter_name && !DWH.adapters.keys.map(&:to_s).include?(adapter_name)
    say "Error: '#{adapter_name}' is not a supported adapter", :red
    say "Supported adapters: #{DWH.adapters.keys.join(", ")}", :yellow
    return
  end

  adapter = adapter_name || prompt.select("Choose adapter:", DWH.adapters.keys.map(&:to_s))
  default_key = generate_default_ds_key(adapter)
  ds_key = prompt.ask("Datasource key (unique identifier):", default: default_key) do |q|
    q.required true
    q.validate(/\A[a-z_][a-z0-9_]*\z/i,
      "Key must be alphanumeric with underscores, starting with a letter or underscore")
  end

  say "\n  Configure #{adapter} datasource (press Enter to accept defaults):\n", :yellow

  # Collect common fields
  config = {"adapter" => adapter}
  config["name"] = prompt.ask("  Display name:", default: ds_key.upcase)
  config["description"] = prompt.ask("  Description:", default: "#{adapter.capitalize} datasource")
  config["tier"] = prompt.select("  Tier:", %w[hot warm cold], default: "warm")
  config["query_timeout"] = prompt.ask("  Query timeout (seconds):", default: "3600", convert: :int)

  # Collect adapter-specific fields
  adapter_fields(adapter).each do |field, default_value|
    config[field] = if field == "auth_mode"
      prompt.select("  Authentication mode:", %w[pat kp oauth], default: default_value)
    elsif %w[ssl azure].include?(field)
      prompt.yes?("  #{field.tr("_", " ").capitalize}?", default: default_value)
    elsif field == "port"
      prompt.ask("  #{field.capitalize}:", default: default_value.to_s, convert: :int)
    else
      prompt.ask("  #{humanize_field(field)}:", default: default_value)
    end
  end

  require_relative "../generators/datasource"
  generator = Generators::Datasource.new([adapter, ds_key], options.merge(config: config))
  generator.invoke_all

  say "\n✔ Added #{adapter} config to datasources.yml", :green

  # Automatically collect credentials if required
  creds = Credentials.new(adapter)
  if creds.required?
    say "\n  Now let's set up credentials:\n", :yellow
    say "  Note: Credentials are stored securely in the local .strata file", :cyan
    say "  and are NOT committed to the repository (ensured by .gitignore).", :cyan
    say ""
    creds.collect
    creds.write_local(ds_key, self)
    say "\n✔ Credentials saved to .strata file", :green
  else
    say "\n  No credentials required for #{adapter}.", :yellow
  end

  # AI Setup
  if ai_not_configured? && prompt.yes?("\n  Enable AI-powered features?", default: true)
    collect_ai_config(prompt)
  elsif ai_not_configured?
    say "\n  AI features skipped. You can configure them later with 'strata ds auth' or manually in .strata",
      :yellow
  end

  say "\n✔ Datasource '#{ds_key}' is ready!", :green
end

#auth(ds_key) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/strata/cli/sub_commands/datasource.rb', line 133

def auth(ds_key)
  unless datasources[ds_key]
    say "Error: Datasource '#{ds_key}' not found in datasources.yml", :red
    return
  end

  adapter = datasources[ds_key]["adapter"]
  creds = Credentials.new(adapter)

  unless creds.required?
    say "Credentials not required for #{adapter} adapter.", :yellow
    return
  end

  say "\nEnter credentials for #{ds_key}", :red
  say "  Note: Credentials are stored securely in the local .strata file", :cyan
  say "  and are NOT committed to the repository (ensured by .gitignore).", :cyan
  say ""
  creds.collect
  creds.write_local(ds_key, self)

  say "Credentials saved successfully to .strata file.", :green
end

#checkObject



30
31
32
33
34
35
36
37
38
# File 'lib/strata/cli/sub_commands/datasource.rb', line 30

def check
  rows = adapter_gem_status.map do |adapter, info|
    status = info[:installed] ? "" : ""
    [adapter.to_s, info[:gem], status]
  end
  say "\n  Adapter gem status:\n\n", :yellow
  print_table(rows, headers: %w[Adapter Gem Installed], color: :magenta)
  say "\n  Install missing gems with: gem install <gem_name>", :cyan
end

#exec(ds_key) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/strata/cli/sub_commands/datasource.rb', line 216

def exec(ds_key)
  adapter = create_adapter(ds_key)
  if options[:file]
    file_path = validate_file_path(options[:file])
    queries = File.read(file_path).split(";").reject { it.nil? || it.strip == "" }
  elsif options[:query]
    queries = options[:query].split(";").reject { it.nil? || it.strip == "" }
  else
    raise StrataError, "Either a file (-f) or a query (-q) should b submitted."
  end

  queries.each_with_index do |query, index|
    puts ""
    res = with_spinner("running #{index + 1}/#{queries.length} queries...") do
      adapter.execute(query, format: :object)
    end
    print_table(res.map(&:values), headers: res.first.keys)
  end
rescue => e
  say "\n\t!!Failed: #{e.message}", :red
end

#listObject



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/strata/cli/sub_commands/datasource.rb', line 41

def list
  ds = begin
    YAML.safe_load_file("datasources.yml", permitted_classes: [Date, Time], aliases: true) || {}
  rescue Errno::ENOENT
    {}
  end

  if ds.empty?
    say "No datasources configured. Run 'strata datasource add' to add one.", :yellow
    return
  end

  names = ds.keys.map { "#{it} => #{ds[it]["name"]}" }
  out = "\n  #{names.join("\n  ")}"
  say out, :magenta
end

#meta(ds_key, table_name) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/strata/cli/sub_commands/datasource.rb', line 199

def meta(ds_key, table_name)
  say "\n● Schema for table: #{table_name} (#{ds_key}):\n", :yellow
  adapter = create_adapter(ds_key)
  md = adapter.(table_name, **options.transform_keys(&:to_sym))

  headings = md.columns.first.to_h.keys
  rows = md.columns.map(&:to_h).map(&:values)

  say print_table(rows, headers: headings, color: :yellow)
rescue => e
  say "\n\t!!Failed: #{e.message}", :red
end

#tables(ds_key = nil) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/strata/cli/sub_commands/datasource.rb', line 174

def tables(ds_key = nil)
  prompt = TTY::Prompt.new
  ds_key = resolve_datasource(ds_key, prompt: prompt)
  return unless ds_key

  say "\nListing #{ds_key} tables...\n\n", :yellow
  adapter = create_adapter(ds_key)
  tables = adapter.tables(**options)
  tables = tables.select { it =~ /#{options[:pattern]}/ } if options[:pattern]

  if tables.empty?
    say "No tables found.", :yellow
    return
  end

  # Use interactive list for browsing
  prompt.select("Tables in #{ds_key} (Type to filter):", tables, per_page: 20, filter: true)
rescue => e
  say "\n\t!!Failed: #{e.message}", :red
end

#test(ds_key) ⇒ Object



159
160
161
162
163
164
165
166
167
# File 'lib/strata/cli/sub_commands/datasource.rb', line 159

def test(ds_key)
  adapter = create_adapter(ds_key)
  with_spinner("Testing #{ds_key} connection...", success_message: "Connected!",
    failed_message: "Failed to connect.") do
    adapter.test_connection(raise_exception: true)
  end
rescue => e
  say "\t!! Failed to connect: \n\t#{e.message}", :red
end