eno 0.4 → 0.5

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: eb697b829ee2858e69995a4485607463b4b2c0740cb78f2c984bd25ffa188108
4
- data.tar.gz: 40850710021d688e6a916e0f8c21ee5511990012f015c8c9541e9e700f815b59
3
+ metadata.gz: f38441c60755b6d8a052a675725904ef2d7694955b81908790c31a1f33a314fd
4
+ data.tar.gz: f7a0cb8679e3c0798f626781daac68c17c5de0aca907323628e26a1b73645d73
5
5
  SHA512:
6
- metadata.gz: cf6d28da3a66cc2db0121914f2ae29c578ec67f9d5ea72bc9acb9fc0d0c85215145f424092095b73abc6d68b094661ef5a73a318f84d3a70d2dba91c1f75a4b7
7
- data.tar.gz: dec76ece5872f0f4688d1654ff2894809680e870f728165269516167101828be9e8379f77ef643f137604f4cf082a73d03c88ce0a05be34248350f32b40081b7
6
+ metadata.gz: 94a69f5cdd6e83f504d2bf3536e5616b1cce54589a450dc25440ead3576b93def237be2e51e74cdffb9671775bd22b5f695c7f0db92fd5d8cde482744442b84e
7
+ data.tar.gz: 1e3859cfb9f114e90b8748862a30db06e41c57c54754bb1c0d6c768c11cab065dd2d75fcc013494490ec0f44defda21e4e5d52fbb2920baebb1ca32b548b5962
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ * Rename `#_q` to `#_l` (for literal)
2
+ * Add `#_i` method for creating identifier
3
+
4
+ 0.5 2019-01-25
5
+ --------------
6
+
7
+ * Implement query combination: `union`, `intersect`, `except`
8
+ * Implement `#not_in` method
9
+ * Implement case expression (using `#cond`)
10
+ * Implement not in expression
11
+ * Implement in operator
12
+ * Implement cast operator (using either `#cast` or `#^`)
13
+
1
14
  0.4 2019-01-21
2
15
  --------------
3
16
 
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ eno (0.5)
5
+ modulation (= 0.18)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ modulation (0.18)
11
+ pg (1.1.3)
12
+ sqlite3 (1.3.13)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ eno!
19
+ pg (= 1.1.3)
20
+ sqlite3 (= 1.3.13)
21
+
22
+ BUNDLED WITH
23
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 digital-fabric
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -30,7 +30,7 @@ like ActiveRecord's `where`:
30
30
  Client.where(order_count: [1, 3, 5])
31
31
  ```
32
32
 
33
- And Sequel is a bit more flexible:
33
+ And Sequel is (quite) a bit more flexible:
34
34
 
35
35
  ```ruby
36
36
  Client.where { order_count > 10 }
@@ -98,15 +98,90 @@ Q {
98
98
  }.to_sql #=> "select a, b from c"
99
99
  ```
100
100
 
101
- ## Using expressions
101
+ ## Expressions
102
102
 
103
- Once inside the query block, you can build arbitrarily complex expressions. You
104
- can mix logical and arithmetic operators:
103
+ Eno lets you build arbitrarily complex expressions once inside the query block.
104
+ You can freely mix identifiers and literals, use most operators (with certain
105
+ caveats) and make function calls.
106
+
107
+ ### Identifiers
108
+
109
+ An identifier is referenced simply using its name:
110
+
111
+ ```ruby
112
+ Q {
113
+ select foo
114
+ } #=> select foo
115
+ ```
116
+
117
+ Identifiers can be qualified by using dot-notation:
118
+
119
+ ```ruby
120
+ Q {
121
+ select foo.bar
122
+ } #=> select foo.bar
123
+ ```
124
+
125
+ ### Literals
126
+
127
+ Literals can be specified as literals
128
+
129
+ ```ruby
130
+ Q {
131
+ select x * 10
132
+ } #=> select x * 10
133
+ ```
134
+
135
+ However, if the first argument of an expression is a literal, it will need to be
136
+ wrapped in a call to `#_q`:
137
+
138
+ ```ruby
139
+ Q {
140
+ select _q(2) + 2
141
+ } #=> select 2 + 2
142
+ ```
143
+
144
+ ### Operators
145
+
146
+ Eno supports the following mathematical operators:
147
+
148
+ operator | description
149
+ ---------|------------
150
+ `+` | addition
151
+ `-` | subtraction
152
+ `*` | multiplication
153
+ `/` | division
154
+ `%` | modulo (remainder)
155
+
156
+ Logical operators are supported using the following operators:
157
+
158
+ operator | description
159
+ ---------|------------
160
+ `&` | logical and
161
+ `\|` | logical or
162
+ `!` | logical not
163
+
164
+ The following comparison operators are supported:
165
+
166
+ operator | description
167
+ ---------|------------
168
+ `==` | equal
169
+ `!=` | not equal
170
+ `<` | less than
171
+ `>` | greater than
172
+ `<=` | less than or equal
173
+ `>=` | greater than or equal
174
+
175
+ An example involving multiple operators:
105
176
 
106
177
  ```ruby
107
- Q { select (a + b) & (c * d) }.to_sql #=> select (a + b) and (c * d)
178
+ Q {
179
+ select (a + b) & (c * d), e >= f
180
+ } #=> select (a + b) and (c * d), e >= f
108
181
  ```
109
182
 
183
+ ### functions
184
+
110
185
  You can also use SQL functions:
111
186
 
112
187
  ```ruby
@@ -117,6 +192,80 @@ Q {
117
192
  }
118
193
  ```
119
194
 
195
+ ## SQL clauses
196
+
197
+ Eno supports the following clauses:
198
+
199
+ ### Select
200
+
201
+ The `#select` method is used to specify the list of selected expressions for a
202
+ `select` statement. The `select` method accepts a list of expressions:
203
+
204
+ ```ruby
205
+ Q { select a, b + c, d.as(e) } #=> select a, b + c, d as e
206
+ ```
207
+
208
+ The `#select` method can also accept a hash mapping aliases to expressions:
209
+
210
+ ```ruby
211
+ Q { select c: a + b, f: d(e) } #=> select a + b as c, d(e) as f
212
+ ```
213
+
214
+ Columns can be qualified using dot-notation:
215
+
216
+ ```ruby
217
+ Q { select a.b, c.d.e } #=> select a.b, c.d.e
218
+ ```
219
+
220
+ Note: if `#select` is not called within a query block, a `select *` is assumed:
221
+
222
+ ```ruby
223
+ Q { from mytable } #=> select * from mytable
224
+ ```
225
+
226
+ ### From
227
+
228
+ The `#from` method is used to specify one or more sources for the query. Usually
229
+ this would be a table name, a subquery, a CTE name (specified using `#with`):
230
+
231
+ ```ruby
232
+ Q { from a, b, c } #=> select * from a, b, c
233
+ Q { from a.as b } #=> select * from a as b
234
+ ```
235
+
236
+ Subqueries can also be used in `#from`:
237
+
238
+ ```ruby
239
+ Q {
240
+ select sum(foo.score)
241
+ from Q { select * from scores }.as(foo)
242
+ } #=> select sum(foo.score) from (select score from scores) as foo
243
+ ```
244
+
245
+ ### Where
246
+
247
+ The `#where` method is used to specify a record filter:
248
+
249
+ ```ruby
250
+ Q {
251
+ from users
252
+ where name == 'John Doe' & age > 30
253
+ } #=> select * from users where (name = 'John Doe') and (age > 30)
254
+ ```
255
+
256
+ Where clauses can be of arbitrary complexity (as shown [above](#expressions)),
257
+ and can also be chained in order to mutate and further filter query:
258
+
259
+ ```ruby
260
+ query = Q {
261
+ from users
262
+ where state == 'CA'
263
+ }
264
+ query.where { age >= 25 } #=> select * from users where (state = 'CA') and (age >= 25)
265
+ ```
266
+
267
+
268
+
120
269
  ## Hooking up Eno to your database
121
270
 
122
271
  In and of itself, Eno is just an engine for building SQL queries. To actually
data/eno.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ require_relative './lib/eno/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'eno'
5
+ s.version = Eno::VERSION
6
+ s.licenses = ['MIT']
7
+ s.summary = 'Eno: Eno is Not an ORM'
8
+ s.author = 'Sharon Rosner'
9
+ s.email = 'ciconia@gmail.com'
10
+ s.files = `git ls-files`.split
11
+ s.homepage = 'http://github.com/digital-fabric/eno'
12
+ s.metadata = {
13
+ "source_code_uri" => "https://github.com/digital-fabric/eno"
14
+ }
15
+ s.rdoc_options = ["--title", "eno", "--main", "README.md"]
16
+ s.extra_rdoc_files = ["README.md"]
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency 'modulation', '0.18'
20
+ s.add_development_dependency 'pg', '1.1.3'
21
+ s.add_development_dependency 'sqlite3', '1.3.13'
22
+ end
data/examples/basic.rb ADDED
File without changes
data/lib/eno.rb CHANGED
@@ -2,485 +2,16 @@
2
2
 
3
3
  require 'modulation/gem'
4
4
 
5
- export :Query, :SQL, :Expression, :Identifier, :Alias
5
+ export_default :Eno
6
6
 
7
7
  module ::Kernel
8
8
  def Q(**ctx, &block)
9
- Query.new(**ctx, &block)
9
+ Eno::Query.new(**ctx, &block)
10
10
  end
11
11
  end
12
12
 
13
- class Expression
14
- def self.quote(expr)
15
- case expr
16
- when Query
17
- "(#{expr.to_sql.strip})"
18
- when Expression
19
- expr.to_sql
20
- when Symbol
21
- expr.to_s
22
- when String
23
- "'#{expr}'"
24
- else
25
- expr.inspect
26
- end
27
- end
28
-
29
- attr_reader :members, :props
30
-
31
- def initialize(*members, **props)
32
- @members = members
33
- @props = props
34
- end
35
-
36
- def as(sym = nil, &block)
37
- if sym
38
- Alias.new(self, sym)
39
- else
40
- Alias.new(self, Query.new(&block))
41
- end
42
- end
43
-
44
- def desc
45
- Desc.new(self)
46
- end
47
-
48
- def over(sym = nil, &block)
49
- Over.new(self, sym || WindowExpression.new(&block))
50
- end
51
-
52
- def ==(expr2)
53
- Operator.new('=', self, expr2)
54
- end
55
-
56
- def !=(expr2)
57
- Operator.new('<>', self, expr2)
58
- end
59
-
60
- def <(expr2)
61
- Operator.new('<', self, expr2)
62
- end
63
-
64
- def >(expr2)
65
- Operator.new('>', self, expr2)
66
- end
67
-
68
- def <=(expr2)
69
- Operator.new('<=', self, expr2)
70
- end
71
-
72
- def >=(expr2)
73
- Operator.new('>=', self, expr2)
74
- end
75
-
76
- def &(expr2)
77
- Operator.new('and', self, expr2)
78
- end
79
-
80
- def |(expr2)
81
- Operator.new('or', self, expr2)
82
- end
83
-
84
- def +(expr2)
85
- Operator.new('+', self, expr2)
86
- end
87
-
88
- def -(expr2)
89
- Operator.new('-', self, expr2)
90
- end
91
-
92
- def *(expr2)
93
- Operator.new('*', self, expr2)
94
- end
95
-
96
- def /(expr2)
97
- Operator.new('/', self, expr2)
98
- end
99
-
100
- def %(expr2)
101
- Operator.new('%', self, expr2)
102
- end
103
-
104
- # not
105
- def !@
106
- Not.new(self)
107
- end
108
-
109
- def null?
110
- IsNull.new(self)
111
- end
112
-
113
- def not_null?
114
- IsNotNull.new(self)
115
- end
116
-
117
- def join(sym, **props)
118
- Join.new(self, sym, **props)
119
- end
120
-
121
- def inner_join(sym, **props)
122
- join(sym, props.merge(type: :inner))
123
- end
124
- end
125
-
126
- class Operator < Expression
127
- def initialize(*members, **props)
128
- op = members[0]
129
- if Operator === members[1] && op == members[1].op
130
- members = [op] + members[1].members[1..-1] + members[2..-1]
131
- end
132
- if Operator === members[2] && op == members[2].op
133
- members = members[0..1] + members[2].members[1..-1]
134
- end
135
-
136
- super(*members, **props)
137
- end
138
-
139
- def op
140
- @members[0]
141
- end
142
-
143
- def to_sql
144
- op = " #{@members[0]} "
145
- "(%s)" % @members[1..-1].map { |m| Expression.quote(m) }.join(op)
146
- end
147
- end
148
-
149
- class Desc < Expression
150
- def to_sql
151
- "#{Expression.quote(@members[0])} desc"
152
- end
153
- end
154
-
155
- class Over < Expression
156
- def to_sql
157
- "#{Expression.quote(@members[0])} over #{Expression.quote(@members[1])}"
158
- end
159
- end
160
-
161
- class Not < Expression
162
- def to_sql
163
- "(not #{Expression.quote(@members[0])})"
164
- end
165
- end
166
-
167
- class IsNull < Expression
168
- def to_sql
169
- "(#{Expression.quote(@members[0])} is null)"
170
- end
171
-
172
- def !@
173
- IsNotNull.new(members[0])
174
- end
175
- end
176
-
177
- class IsNotNull < Expression
178
- def to_sql
179
- "(#{Expression.quote(@members[0])} is not null)"
180
- end
181
- end
182
-
183
- class WindowExpression < Expression
184
- def initialize(&block)
185
- instance_eval(&block)
186
- end
187
-
188
- def partition_by(*args)
189
- @partition_by = args
190
- end
191
-
192
- def order_by(*args)
193
- @order_by = args
194
- end
195
-
196
- def range_unbounded
197
- @range = 'between unbounded preceding and unbounded following'
198
- end
199
-
200
- def to_sql
201
- "(%s)" % [
202
- _partition_by_clause,
203
- _order_by_clause,
204
- _range_clause
205
- ].join.strip
206
- end
207
-
208
- def _partition_by_clause
209
- return nil unless @partition_by
210
- "partition by %s " % @partition_by.map { |e| Expression.quote(e) }.join(', ')
211
- end
212
-
213
- def _order_by_clause
214
- return nil unless @order_by
215
- "order by %s " % @order_by.map { |e| Expression.quote(e) }.join(', ')
216
- end
217
-
218
- def _range_clause
219
- return nil unless @range
220
- "range #{@range} "
221
- end
222
-
223
- def method_missing(sym)
224
- super if sym == :to_hash
225
- Identifier.new(sym)
226
- end
227
- end
228
-
229
- class QuotedExpression < Expression
230
- def to_sql
231
- Expression.quote(@members[0])
232
- end
233
- end
234
-
235
- class Identifier < Expression
236
- def to_sql
237
- @members[0].to_s
238
- end
239
-
240
- def method_missing(sym)
241
- super if sym == :to_hash
242
- Identifier.new("#{@members[0]}.#{sym}")
243
- end
244
-
245
- def _empty_placeholder?
246
- m = @members[0]
247
- Symbol === m && m == :_
248
- end
249
- end
250
-
251
- class Alias < Expression
252
- def to_sql
253
- "#{Expression.quote(@members[0])} as #{Expression.quote(@members[1])}"
254
- end
255
- end
256
-
257
- class FunctionCall < Expression
258
- def to_sql
259
- fun = @members[0]
260
- if @members.size == 2 && Identifier === @members.last && @members.last._empty_placeholder?
261
- "#{fun}()"
262
- else
263
- "#{fun}(#{@members[1..-1].map { |a| Expression.quote(a) }.join(', ')})"
264
- end
265
- end
266
- end
267
-
268
- class Join < Expression
269
- H_JOIN_TYPES = {
270
- nil: 'join',
271
- inner: 'inner join',
272
- outer: 'outer join'
273
- }
274
-
275
- def to_sql
276
- ("%s %s %s %s" % [
277
- Expression.quote(@members[0]),
278
- H_JOIN_TYPES[@props[:type]],
279
- Expression.quote(@members[1]),
280
- condition_sql
281
- ]).strip
282
- end
283
-
284
- def condition_sql
285
- if @props[:on]
286
- 'on %s' % Expression.quote(@props[:on])
287
- elsif using_fields = @props[:using]
288
- fields = using_fields.is_a?(Array) ? using_fields : [using_fields]
289
- 'using (%s)' % fields.map { |f| Expression.quote(f) }.join(', ')
290
- else
291
- nil
292
- end
293
- end
294
- end
295
-
296
- class From < Expression
297
- def to_sql
298
- "from %s" % @members.map { |m| member_sql(m) }.join(', ')
299
- end
300
-
301
- def member_sql(member)
302
- if Query === member
303
- "%s t1" % Expression.quote(member)
304
- elsif Alias === member && Query === member.members[0]
305
- "%s %s" % [Expression.quote(member.members[0]), Expression.quote(member.members[1])]
306
- else
307
- Expression.quote(member)
308
- end
309
- end
310
- end
311
-
312
- class With < Expression
313
- def to_sql
314
- "with %s" % @members.map { |e| Expression.quote(e) }.join(', ')
315
- end
316
- end
317
-
318
- class Select < Expression
319
- def to_sql
320
- "select %s%s" % [distinct_clause, @members.map { |e| Expression.quote(e) }.join(', ')]
321
- end
322
-
323
- def distinct_clause
324
- case (on = @props[:distinct])
325
- when nil
326
- nil
327
- when true
328
- "distinct "
329
- when Array
330
- "distinct on (%s) " % on.map { |e| Expression.quote(e) }.join(', ')
331
- else
332
- "distinct on %s " % Expression.quote(on)
333
- end
334
- end
335
- end
336
-
337
- class Where < Expression
338
- def to_sql
339
- "where %s" % @members.map { |e| Expression.quote(e) }.join(' and ')
340
- end
341
- end
342
-
343
- class Window < Expression
344
- def initialize(sym, &block)
345
- super(sym)
346
- @block = block
347
- end
348
-
349
- def to_sql
350
- "window %s as %s" % [
351
- Expression.quote(@members.first),
352
- WindowExpression.new(&@block).to_sql
353
- ]
354
- end
355
- end
356
-
357
- class OrderBy < Expression
358
- def to_sql
359
- "order by %s" % @members.map { |e| Expression.quote(e) }.join(', ')
360
- end
361
- end
362
-
363
- class Limit < Expression
364
- def to_sql
365
- "limit %d" % @members[0]
366
- end
367
- end
368
-
369
- class Query
370
- def initialize(**ctx, &block)
371
- @ctx = ctx
372
- @block = block
373
- end
374
-
375
- def to_sql(**ctx)
376
- r = SQL.new(@ctx.merge(ctx))
377
- r.to_sql(&@block)
378
- end
379
-
380
- def as(sym)
381
- Alias.new(self, sym)
382
- end
383
-
384
- def where(&block)
385
- old_block = @block
386
- Query.new(@ctx) {
387
- instance_eval(&old_block)
388
- where instance_eval(&block)
389
- }
390
- end
391
-
392
- def mutate(&block)
393
- old_block = @block
394
- Query.new(@ctx) {
395
- instance_eval(&old_block)
396
- instance_eval(&block)
397
- }
398
- end
399
- end
400
-
401
- class SQL
402
- def initialize(ctx)
403
- @ctx = ctx
404
- end
405
-
406
- def to_sql(&block)
407
- instance_eval(&block)
408
- [
409
- @with,
410
- @select || default_select,
411
- @from,
412
- @where,
413
- @window,
414
- @order_by,
415
- @limit
416
- ].compact.map { |c| c.to_sql }.join(' ')
417
- end
418
-
419
- def _q(expr)
420
- QuotedExpression.new(expr)
421
- end
422
-
423
- def default_select
424
- Select.new(:*)
425
- end
426
-
427
- def method_missing(sym, *args)
428
- if @ctx.has_key?(sym)
429
- value = @ctx[sym]
430
- return Symbol === value ? Identifier.new(value) : value
431
- end
432
-
433
- super if sym == :to_hash
434
- if args.empty?
435
- Identifier.new(sym)
436
- else
437
- FunctionCall.new(sym, *args)
438
- end
439
- end
440
-
441
- def with(*members, **props)
442
- @with = With.new(*members, **props)
443
- end
444
-
445
- H_EMPTY = {}.freeze
446
-
447
- def select(*members, **props)
448
- if members.empty? && !props.empty?
449
- members = props.map { |k, v| Alias.new(v, k) }
450
- props = {}
451
- end
452
- @select = Select.new(*members, **props)
453
- end
454
-
455
- def from(*members, **props)
456
- @from = From.new(*members, **props)
457
- end
458
-
459
- def where(expr)
460
- if @where
461
- @where.members << expr
462
- else
463
- @where = Where.new(expr)
464
- end
465
- end
466
-
467
- def window(sym, &block)
468
- @window = Window.new(sym, &block)
469
- end
470
-
471
- def order_by(*members, **props)
472
- @order_by = OrderBy.new(*members, **props)
473
- end
474
-
475
- def limit(*members)
476
- @limit = Limit.new(*members)
477
- end
478
-
479
- def all(sym = nil)
480
- if sym
481
- Identifier.new("#{sym}.*")
482
- else
483
- Identifier.new('*')
484
- end
485
- end
486
- end
13
+ module Eno
14
+ include_from('./eno/expressions')
15
+ SQL = import('./eno/sql')::SQL
16
+ Query = import('./eno/query')::Query
17
+ end