querylet 1.0.0 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eae3b4c9cb1beeea8edfbb41bab195ad133ac2c91d6393942f942d075860324
4
- data.tar.gz: 6632e474232ac27c6e64cd3d991b65e5331e61e97116280b9b8a9b00c1702a5f
3
+ metadata.gz: 89d1a03b5028328b79f0d575c2aeadec57ebf47f1d743fe5722b4c4a4b165fc6
4
+ data.tar.gz: 5db0889a5a8fc5a21aca04bb27f406522a22de46f91ef1fd95af5f0137983156
5
5
  SHA512:
6
- metadata.gz: c33031f332be7c159ce11f3fb4ec86cf8b606125ad3cf51cfd28c4aa7bb3600d2d5188b7bcb91664bc7b00d307cd258784b09fed852e5886cc8cd4e243ea22d6
7
- data.tar.gz: 1d5b1e8f732f7dd43dd110c442528a5af5b6d9939593d57bb5a9f5724e2e9d8fab9083265a885f2e57977f6612724babdc3a451aa0c786b0c4f7ab0edca19c30
6
+ metadata.gz: 296e9452984b0099681d8567ac291e1c71e9ee62cb706d96668927c0e2cab8062ac54f0c1186eed56bdf2e0a93ee463602a75928525fcb1d02eefe3089fae665
7
+ data.tar.gz: 3b1f422e8cabd1ee7dfbb977df2e3b77ed4d28a6b5bb93f0007cf20aff608737276a8504ca40239671dcd8b66d4e4f8bacaf2a8db92a62d746c200a5e7d7f687
data/README.md CHANGED
@@ -1,21 +1,118 @@
1
- ## Querylet
1
+ # Querylet
2
+
3
+ Querylet is a templating language for SQL
4
+ and is designed to make writing raw SQL much easier.
5
+
6
+ ```sql
7
+ SELECT
8
+ questions.id,
9
+ questions.uuid,
10
+ questions.question_html as question,
11
+ questions.target_type as kind,
12
+ {{#object}}
13
+ SELECT
14
+ case_study_questions.menu,
15
+ case_study_questions.questions_pool_size,
16
+
17
+ CASE {{str sub_question_type}}
18
+ WHEN 'MultiChoiceQuestion' THEN
19
+ {{> object 'questions.multiple_choice' id='{{sub_question_id}}' }} as question
20
+ WHEN 'MultiSelectQuestion' THEN
21
+ {{> object 'questions.multiple_select' id='{{sub_question_id}}' }} as question
22
+ END,
23
+
24
+ {{#array}}
25
+ SELECT
26
+ que.id,
27
+ que.uuid,
28
+ que.target_type as kind,
29
+ choices.comment,
30
+ choices.flagged,
31
+ choices.choice_value as answered,
32
+ multiple_select_questions.answers_count
33
+ FROM ts_admin_que.questions que
34
+ LEFT JOIN public.choices ON choices.question_id = que.id AND choices.attempt_id = {{id}}
35
+ LEFT JOIN ts_admin_que.multiple_select_questions ON multiple_select_questions.id = que.target_id AND que.target_type = 'MultipleSelectQuestion'
36
+ WHERE
37
+ que.id = ANY(case_study_questions.question_ids)
38
+ {{/array}} as questions
39
+ {{/object}} as case_study
40
+ FROM ts_admin_que.questions
41
+ LEFT JOIN ts_admin_que.case_study_questions ON case_study_questions.question_id = questions.id
42
+ WHERE
43
+ questions.id = {{id}}
44
+ ```
45
+
46
+ ## Variable Filters
47
+
48
+ ### Int (Integer)
49
+
50
+ Cast variable to integer
51
+
52
+ ```
53
+ {{int my_number}}
54
+ ```
55
+
56
+ ### Str (String)
57
+
58
+ Cast variable to string
2
59
 
3
- #Variables
60
+ ```
61
+ {{str my_string}}
62
+ ```
4
63
 
64
+ ### Float
65
+
66
+ Cast variable to float
67
+
68
+ ```
69
+ {{float my_number}}
70
+ ```
71
+
72
+ ### Arr (Array)
73
+
74
+ Cast to array (not a postgres array)
75
+
76
+ ```
77
+ {{arr my_array}}
78
+ ```
79
+
80
+ ### Wild (Wildcard)
81
+
82
+ Wildcard front and back of string
83
+
84
+ ```
85
+ {{wild my_string}}
86
+ ```
87
+
88
+ ### Variables
89
+
90
+ ```
5
91
  {{my_variable}}
92
+ ```
6
93
 
7
- #Partials
94
+ ### Partials
8
95
 
9
- {{>include 'link.to.path' param='{{testing}}'}}
96
+ ```
97
+ {{> include 'link.to.path' name='andrew' }}
98
+ {{> include 'link.to.path' name={{my_name}} }}
99
+ {{> include 'link.to.path' name={{str my_name}} }}
100
+ ```
101
+
102
+ ### Partial blocks
10
103
 
11
- #Partial blocks
104
+ ```
12
105
  {{#array}}
13
106
  {{/array}}
107
+ ```
14
108
 
15
- # If Else
109
+ ### If Else
110
+
111
+ ```
16
112
  {{#if variable}}
17
113
  {{else}}
18
114
  {{/end}}
115
+ ```
19
116
 
20
117
  ## Parslet
21
118
 
@@ -56,12 +153,10 @@ parser.parse_with_debug(input)
56
153
 
57
154
  ## Parser
58
155
 
59
- Parslet parsers output deep nested hashes.
60
-
61
- To see all the define rules check out `parser_helper.rb`
156
+ To see all the define rules check out `parser_spec.rb`
62
157
 
63
158
  ```
64
- rspec spec/parser_helper.rb
159
+ bundle exec rspec spec/parser_spec.rb
65
160
  ```
66
161
 
67
162
  ## Transform
@@ -1,5 +1,10 @@
1
1
  module Querylet
2
- module Context
2
+ class Context
3
+ def initialize(querylet, data)
4
+ @querylet = querylet
5
+ @data = data
6
+ end
7
+
3
8
  def get(value)
4
9
  @data.merge(locals)[value.to_sym]
5
10
  end
@@ -12,15 +17,21 @@ module Querylet
12
17
  hash.map { |k, v| add_item(k, v) }
13
18
  end
14
19
 
15
- def with_temporary_context(args = {})
16
- saved = args.keys.collect { |key| [key, get(key.to_s)] }.to_h
20
+ def get_partial name, dot_path, data
21
+ @querylet.get_partial name, dot_path, @data.merge(data)
22
+ end
17
23
 
18
- add_items(args)
19
- block_result = yield
20
- locals.merge!(saved)
24
+ # This never appears to be used
25
+ # commented out unless I find a use for it.
26
+ #def with_temporary_context(args = {})
27
+ # saved = args.keys.collect { |key| [key, get(key.to_s)] }.to_h
21
28
 
22
- block_result
23
- end
29
+ # add_items(args)
30
+ # block_result = yield
31
+ # locals.merge!(saved)
32
+
33
+ # block_result
34
+ #end
24
35
 
25
36
  private
26
37
 
@@ -96,7 +96,7 @@ module Querylet
96
96
  rule(:variable) { docurly >> space? >> id.as(:variable) >> space? >> dccurly}
97
97
 
98
98
  # Can either be a variable or a string:
99
- rule(:parameter) { id.as(:variable) | string }
99
+ rule(:parameter) { id.as(:variable) | string }
100
100
 
101
101
  # used in includes eg. hello='world'
102
102
  rule(:parameter_kv) {
@@ -104,7 +104,7 @@ module Querylet
104
104
  space? >>
105
105
  eq >>
106
106
  space? >>
107
- string.as(:value) >>
107
+ (filter | variable | string).as(:value) >>
108
108
  space?
109
109
  }
110
110
 
@@ -147,7 +147,7 @@ module Querylet
147
147
  rule(:nocurly) { match('[^{}]') }
148
148
  rule(:eof) { any.absent? }
149
149
 
150
- # String contained in Single Qoutes 'content'
150
+ # String contained in Single Quotes 'content'
151
151
  rule(:string) { match("'") >> match("[^']").repeat(1).as(:string) >> match("'") }
152
152
  end
153
153
  end
@@ -1,4 +1,4 @@
1
- #require_relative 'context'
1
+ require_relative 'context'
2
2
 
3
3
  module Querylet
4
4
  class Template
@@ -7,13 +7,19 @@ module Querylet
7
7
  @ast = ast
8
8
  end
9
9
 
10
+ # In Ruby, the call method is used to invoke a block, proc, or lambda,
11
+ # which are all different types of callable objects in Ruby.
12
+ #
13
+ # This is how querylet will get called in a developers code.
14
+ #
15
+ # querylet = Querylet::Querylet.new path: 'path/to/sql'
16
+ # querylet.compile(template).call(data)
17
+ # @args is the initial values passed to the template
10
18
  def call(args = nil)
11
- if args
12
- @querylet.set_context(args)
13
- end
19
+ ctx = Context.new(@querylet, args)
14
20
 
15
21
  # AST should return a Querylet::Tree and call its eval method
16
- @ast.eval(@querylet)
22
+ @ast.eval(ctx)
17
23
  end
18
- end
19
- end
24
+ end # Template
25
+ end # Querylet
data/lib/querylet/tree.rb CHANGED
@@ -36,10 +36,12 @@ module Querylet
36
36
 
37
37
  class Partial < TreeItem.new(:partial, :path, :parameters)
38
38
  def _eval(context)
39
+ data = {}
39
40
  [parameters].flatten.map(&:values).map do |vals|
40
- context.add_item vals.first.to_s, vals.last._eval(context)
41
+ # we have to set it as sym so it overrides correctly
42
+ data[vals.first.to_sym] = vals.last._eval(context)
41
43
  end
42
- content = context.get_partial(partial.to_s, path)
44
+ content = context.get_partial(partial.to_s, path, data)
43
45
  if partial == 'array'
44
46
  <<-HEREDOC.chomp
45
47
  (SELECT COALESCE(array_to_json(array_agg(row_to_json(array_row))),'[]'::json) FROM (
@@ -131,6 +133,8 @@ module Querylet
131
133
  else
132
134
  raise "expected input for: #{parameter.item} to be String, Integer or Array"
133
135
  end
136
+ else
137
+ raise "unexpected filter name: #{filter}"
134
138
  end
135
139
  end
136
140
  end
@@ -1,4 +1,4 @@
1
1
  module Querylet
2
- VERSION = "1.0.0"
2
+ VERSION = "1.2.0"
3
3
  end
4
4
 
data/lib/querylet.rb CHANGED
@@ -6,7 +6,6 @@ require_relative 'querylet/context'
6
6
 
7
7
  module Querylet
8
8
  class Querylet
9
- include Context
10
9
  def initialize(path:)
11
10
  @sql_path = path
12
11
  end
@@ -19,17 +18,15 @@ module Querylet
19
18
  #puts deep_nested_hash
20
19
  abstract_syntax_tree = transform.apply deep_nested_hash
21
20
  #puts abstract_syntax_tree
22
- Template.new self, abstract_syntax_tree
23
- end
24
21
 
25
- def set_context(ctx)
26
- @data = ctx
22
+ # Querylet::Template
23
+ Template.new self, abstract_syntax_tree
27
24
  end
28
25
 
29
- def get_partial name, dot_path
26
+ def get_partial name, dot_path, data={}
30
27
  path = @sql_path + '/' + dot_path.to_s.split('.').join('/') + '.sql'
31
28
  template = File.read(path).to_s.chomp
32
- self.compile(template).call(@data)
29
+ self.compile(template).call(data)
33
30
  end
34
31
  end # class Querylet
35
32
  end # module Querylet
data/spec/parser_spec.rb CHANGED
@@ -10,10 +10,9 @@ RSpec.describe Querylet::Parser do
10
10
  end
11
11
 
12
12
  it 'string' do
13
- (SELECT COALESCE(row_to_json(object_row),'{}'::json) FROM (
14
-
15
13
  expect(parser.parse_with_debug("(SELECT COALESCE(row_to_json(object_row),'{}'::json) FROM (")).to eq({items:[{content: "(SELECT COALESCE(row_to_json(object_row),'{}'::json) FROM ("}]})
16
14
  end
15
+
17
16
  it 'variable' do
18
17
  expect(parser.parse('{{worf}}')).to eq({items:[{variable: 'worf'}]})
19
18
  expect(parser.parse('{{ worf}}')).to eq({items:[{variable: 'worf'}]})
@@ -52,8 +51,24 @@ RSpec.describe Querylet::Parser do
52
51
  partial: 'include',
53
52
  path: 'path.to.template',
54
53
  parameters: [
55
- {key: 'hello', value: {string: 'world'}},
56
- {key: 'star', value: {string: 'trek'}}
54
+ {key: 'hello', value: { string: 'world'}},
55
+ {key: 'star' , value: { string: 'trek'}}
56
+ ]
57
+ }]})
58
+
59
+ expect(parser.parse("{{> include 'path.to.template' hello={{world}} }}")).to eq({items:[{
60
+ partial: 'include',
61
+ path: 'path.to.template',
62
+ parameters: [
63
+ {key: 'hello', value: { variable: 'world'}}
64
+ ]
65
+ }]})
66
+
67
+ expect(parser.parse("{{> include 'path.to.template' hello={{int world}} }}")).to eq({items:[{
68
+ partial: 'include',
69
+ path: 'path.to.template',
70
+ parameters: [
71
+ {key: 'hello', value: {filter: 'int', parameter: {variable: 'world'} }}
57
72
  ]
58
73
  }]})
59
74
  end
@@ -23,41 +23,40 @@ describe Querylet::Querylet do
23
23
 
24
24
  context 'helpers' do
25
25
  it 'include' do
26
- query = <<-SQL.chomp
27
- (SELECT
28
- users.email
29
- FROM users
30
- WHERE
31
- users.id = 1) as email
32
- SQL
26
+ query = "(SELECT users.email FROM users WHERE users.id = 1) as email"
33
27
  expect(evaluate("({{> include 'examples.include' }}) as email")).to eq(query)
34
28
  end
35
29
 
36
30
  it 'include with variables' do
37
- query = <<-SQL.chomp
38
- (SELECT
39
- users.email
40
- FROM users
41
- WHERE
42
- users.id = 100) as email
43
- SQL
31
+ query = "(SELECT users.email FROM users WHERE users.id = 100) as email"
44
32
  template = "({{> include 'examples.include_with_vars' }}) as email"
45
33
  expect(evaluate(template, {id: 100})).to eq(query)
46
34
  end
47
35
 
48
36
  it 'include with parameters' do
49
- query = <<-SQL.chomp
50
- (SELECT
51
- users.email,
52
- 'andrew' as name
53
- FROM users
54
- WHERE
55
- users.id = 200) as email
56
- SQL
37
+ query = "(SELECT users.email, 'andrew' as name FROM users WHERE users.id = 200) as email"
57
38
  template = "({{> include 'examples.include_with_params' id='200' name='andrew' }}) as email"
58
39
  expect(evaluate(template, {id: 100})).to eq(query)
59
40
  end
60
41
 
42
+ it 'include with parameters variable' do
43
+ query = "(SELECT users.email FROM users WHERE users.id = 300) as email"
44
+ template = "({{> include 'examples.include_with_params_vars' id={{user_id}} name='andrew' }}) as email"
45
+ expect(evaluate(template, {user_id: 300})).to eq(query)
46
+ end
47
+
48
+ it 'include with parameters filter' do
49
+ query = "(SELECT users.email, 'Andrew' as name FROM users WHERE users.id = 100) as email"
50
+ template = "({{> include 'examples.include_with_params_filter' name={{str my_name}} }}) as email"
51
+ expect(evaluate(template, {my_name: "Andrew", id: 100})).to eq(query)
52
+ end
53
+
54
+ it 'variable overrides' do
55
+ query = "(SELECT questions.id, (SELECT que.id FROM questions que WHERE que.id = 555) as sub FROM questions WHERE questions.id = 100"
56
+ template = "(SELECT questions.id, ({{> include 'examples.vars_overrides_include' xid={{sub_id}} }}) as sub FROM questions WHERE questions.id = {{xid}}"
57
+ expect(evaluate(template, {sub_id: 555, xid: 100})).to eq(query)
58
+ end
59
+
61
60
  it 'object' do
62
61
  query = <<-SQL.chomp
63
62
  (SELECT COALESCE(row_to_json(object_row),'{}'::json) FROM (
@@ -152,5 +151,5 @@ SQL
152
151
  end
153
152
 
154
153
  end # context 'helpers'
155
- end # context 'evaluating'
154
+ end # context 'evaluating'
156
155
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: querylet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ExamPro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-05 00:00:00.000000000 Z
11
+ date: 2023-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet
@@ -74,7 +74,7 @@ homepage: http://exampro.co
74
74
  licenses:
75
75
  - MIT
76
76
  metadata: {}
77
- post_install_message:
77
+ post_install_message:
78
78
  rdoc_options: []
79
79
  require_paths:
80
80
  - lib
@@ -89,11 +89,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  - !ruby/object:Gem::Version
90
90
  version: '0'
91
91
  requirements: []
92
- rubygems_version: 3.1.2
93
- signing_key:
92
+ rubygems_version: 3.4.10
93
+ signing_key:
94
94
  specification_version: 4
95
95
  summary: Querylet
96
96
  test_files:
97
- - spec/spec_helper.rb
98
97
  - spec/parser_spec.rb
99
98
  - spec/querylet_spec.rb
99
+ - spec/spec_helper.rb