strata-cli 0.1.20 → 0.1.22

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.
@@ -24,19 +24,15 @@ module Strata
24
24
  SAMPLE_REPO = "https://github.com/stratasite/tpc-ds-retail-sample"
25
25
  SAMPLE_DIR = "tpc-ds-retail-sample"
26
26
  SAMPLE_PROJECT_UID = "tpc-ds-retail-sample"
27
- DEFAULT_SERVER = "http://localhost:8080"
28
27
 
29
28
  desc "sample", "Clone the Strata sample project and set it up interactively"
30
29
  option :api_key, aliases: ["a"], type: :string, desc: "Strata API key (optional — skip browser auth)"
31
30
  option :server, aliases: ["s"], type: :string, desc: "Strata server URL (optional — prompted if omitted)"
32
31
  def sample
33
- if options[:api_key] && options[:server]
34
- server = options[:server].delete_suffix("/")
35
- api_key = options[:api_key]
36
- else
37
- server = (options[:server] || prompt.ask("Strata server URL:", default: DEFAULT_SERVER) { |q| q.modify :strip }).delete_suffix("/")
38
- api_key = fetch_api_key_via_browser(server)
39
- end
32
+ server = Configuration.resolve_server(
33
+ options[:server] || prompt.ask("Strata server URL:", default: Configuration::DEFAULT_SERVER) { |q| q.modify :strip }
34
+ )
35
+ api_key = options[:api_key] || fetch_api_key_via_browser(server)
40
36
 
41
37
  if File.directory?(SAMPLE_DIR)
42
38
  print_info("Directory '#{SAMPLE_DIR}' already exists — re-using it")
@@ -51,9 +47,10 @@ module Strata
51
47
  File.write(".gitignore", ".strata\n.strata_cli/\n")
52
48
 
53
49
  strata_path = ".strata"
54
- File.write(strata_path, "api_key: #{api_key}\nserver: #{server}\n")
50
+ File.write(strata_path, "api_key: #{api_key}\n")
55
51
  File.chmod(0o600, strata_path)
56
52
 
53
+ Helpers::ProjectHelper.persist_server_to_project_yml(server)
57
54
  Helpers::ProjectHelper.persist_project_id_to_yml(SAMPLE_PROJECT_UID)
58
55
 
59
56
  system("git add -A", exception: true)
@@ -34,7 +34,9 @@ module Strata
34
34
  end
35
35
 
36
36
  private_class_method def collect_yml_files(project_path)
37
- Dir.glob(File.join(project_path, "**", "*.yml")).select do |file_path|
37
+ # The server globs models as {yml,yaml} (deploy_job.rb:121,162), so a
38
+ # .yaml model has to ship too -- it used to be left out of the archive.
39
+ Dir.glob(File.join(project_path, "**", "*.{yml,yaml}")).select do |file_path|
38
40
  File.file?(file_path)
39
41
  end
40
42
  end
@@ -5,7 +5,19 @@ module Strata
5
5
  class ExpressionParser
6
6
  attr_reader :expression, :columns, :field_refs, :measure_refs, :dimension_refs, :functions
7
7
 
8
- def initialize(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions)
8
+ # The identifier quote for ANSI SQL and any adapter whose quote setting is
9
+ # bracket-style: [Name] is Strata's field-reference syntax, so an adapter
10
+ # that quotes with brackets (SQL Server) quotes identifiers with double
11
+ # quotes in expressions instead.
12
+ ANSI_QUOTE = '"@exp"'
13
+
14
+ # +quote+ is the adapter's identifier quote template from its DWH settings
15
+ # ("@exp" wrapped in the warehouse's quote characters). Only that quote
16
+ # marks an identifier: on a backtick warehouse (MySQL, BigQuery, ...) a
17
+ # double-quoted span is a string literal, not a column.
18
+ def initialize(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions, quote: ANSI_QUOTE)
19
+ @open, @close = self.class.quote_pair(quote)
20
+ @raw = expression
9
21
  @expression = expression.downcase
10
22
  @reserved = reserved
11
23
  @aggregates = aggregates
@@ -46,7 +58,7 @@ module Strata
46
58
  stack = [] # one entry per open paren: is it an aggregate call?
47
59
  # Bracketed references are consumed atomically FIRST, so a parenthesis inside a
48
60
  # reference name ([Month(Date)]) cannot unbalance the stack.
49
- remove_string_literals(@expression).scan(/\[([^\]]+)\]@d|\[[^\]]+\](?:@m)?|([a-z_][a-z0-9_]*)?\(|\)/) do
61
+ scrub(@expression).scan(/\[([^\]]+)\]@d|\[[^\]]+\](?:@m)?|([a-z_][a-z0-9_]*)?\(|\)/) do
50
62
  token = Regexp.last_match(0)
51
63
  if Regexp.last_match(1)
52
64
  ref = Regexp.last_match(1).strip
@@ -64,6 +76,22 @@ module Strata
64
76
  end
65
77
 
66
78
  class << self
79
+ # The opening and closing identifier quote characters an adapter's quote
80
+ # template ("@exp", `@exp`, [@exp]) wraps around a name. Bracket quoting
81
+ # is Strata's field-reference syntax, so it falls back to ANSI quotes.
82
+ def quote_pair(template)
83
+ template = template.to_s
84
+ template = ANSI_QUOTE if template.strip.empty? || template.start_with?("[")
85
+ open, close = template.split("@exp", 2)
86
+ [open, (close.nil? || close.empty?) ? open : close]
87
+ end
88
+
89
+ # String literals: single-quoted always, double-quoted too on a
90
+ # warehouse whose identifier quote is something else (MySQL, BigQuery).
91
+ def literal_regex(open)
92
+ (open == '"') ? /'(?:[^']|'')*'/ : /'(?:[^']|'')*'|"(?:[^"]|"")*"/
93
+ end
94
+
67
95
  # Can this (resolved) SQL expression legally appear in a GROUP BY? Aggregates and
68
96
  # windows evaluate over the grouped rows, so an expression carrying either cannot —
69
97
  # the binder rejects it ("GROUP BY clause cannot contain aggregates").
@@ -86,16 +114,19 @@ module Strata
86
114
  sql.gsub(/(?<!")\b#{Regexp.quote(col)}\b(?!")/i, qualified)
87
115
  end
88
116
 
89
- def parse(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions)
90
- new(expression, reserved: reserved, aggregates: aggregates)
117
+ def parse(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions, quote: ANSI_QUOTE)
118
+ new(expression, reserved: reserved, aggregates: aggregates, quote: quote)
91
119
  end
92
120
  end
93
121
 
94
122
  private
95
123
 
96
124
  def parse
97
- # Sanitize the expression first
98
- sanitized = remove_string_literals(@expression)
125
+ # Quoted identifiers are collected from the original text -- their case
126
+ # is part of the name -- and scrubbed out of the working copy along with
127
+ # string literals, so nothing inside them reads as a function or column.
128
+ @quoted_identifiers = []
129
+ sanitized = scrub(@raw) { |ident| @quoted_identifiers << ident unless @quoted_identifiers.include?(ident) }.downcase
99
130
 
100
131
  matched_refs = []
101
132
  # Extract field references in bracketed notation — BEFORE scanning functions, so a
@@ -123,10 +154,7 @@ module Strata
123
154
  # grouping — which is what has_window_functions? reports.
124
155
  @windowed = sanitized.match?(/\bover\s*\(/)
125
156
 
126
- # Extract quoted columns
127
- @expression.scan(/"([^"]+)"/).flatten.each do |col|
128
- @columns << col
129
- end
157
+ @columns.concat(@quoted_identifiers)
130
158
 
131
159
  # Extract column references (alphanumeric with optional underscores)
132
160
  sanitized.scan(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/).flatten.each do |col|
@@ -136,8 +164,21 @@ module Strata
136
164
  @wild = true if sanitized.match?(/\w+\(\s?\*\s?\)/)
137
165
  end
138
166
 
139
- def remove_string_literals(expr)
140
- expr.gsub(/'[^']*'/, "'__str__'")
167
+ # Replaces string literals with a placeholder literal and quoted
168
+ # identifiers with a numeric placeholder (yielding each identifier's
169
+ # name), leaving an expression whose remaining words are all bare
170
+ # references, keywords, or functions. Inside a quoted identifier a
171
+ # doubled quote character is an escaped quote.
172
+ def scrub(expr)
173
+ o, c = Regexp.quote(@open), Regexp.quote(@close)
174
+ expr.gsub(/#{self.class.literal_regex(@open)}|#{o}((?:[^#{c}]|#{c}#{c})+)#{c}/) do
175
+ if Regexp.last_match(1)
176
+ yield Regexp.last_match(1) if block_given?
177
+ "0"
178
+ else
179
+ "'__str__'"
180
+ end
181
+ end
141
182
  end
142
183
 
143
184
  def remove_matched_refs(sanitized, matched_refs)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Strata
4
4
  module CLI
5
- VERSION = "0.1.20"
5
+ VERSION = "0.1.22"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strata-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.20
4
+ version: 0.1.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajo Abraham
@@ -348,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
348
348
  - !ruby/object:Gem::Version
349
349
  version: '0'
350
350
  requirements: []
351
- rubygems_version: 3.6.9
351
+ rubygems_version: 3.6.7
352
352
  specification_version: 4
353
353
  summary: Command-line interface for the Strata Semantic Analytics System
354
354
  test_files: []