alf 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +89 -0
- data/Gemfile.lock +6 -1
- data/README.md +35 -21
- data/TODO.md +0 -5
- data/alf.gemspec +2 -0
- data/alf.noespec +6 -4
- data/bin/alf +9 -13
- data/examples/{autonum.alf → operators/autonum.alf} +0 -0
- data/examples/{cities.rash → operators/cities.rash} +0 -0
- data/examples/{clip.alf → operators/clip.alf} +0 -0
- data/examples/{compact.alf → operators/compact.alf} +0 -0
- data/examples/{database.alf → operators/database.alf} +1 -1
- data/examples/{defaults.alf → operators/defaults.alf} +0 -0
- data/examples/{extend.alf → operators/extend.alf} +0 -0
- data/examples/{group.alf → operators/group.alf} +0 -0
- data/examples/{intersect.alf → operators/intersect.alf} +0 -0
- data/examples/{join.alf → operators/join.alf} +0 -0
- data/examples/operators/matching.alf +2 -0
- data/examples/{minus.alf → operators/minus.alf} +0 -0
- data/examples/operators/not_matching.alf +2 -0
- data/examples/{nulls.rash → operators/nulls.rash} +0 -0
- data/examples/{parts.rash → operators/parts.rash} +0 -0
- data/examples/{project.alf → operators/project.alf} +0 -0
- data/examples/{pseudo-with.alf → operators/pseudo-with.alf} +0 -0
- data/examples/{quota.alf → operators/quota.alf} +0 -0
- data/examples/operators/rank.alf +4 -0
- data/examples/{rename.alf → operators/rename.alf} +0 -0
- data/examples/{restrict.alf → operators/restrict.alf} +0 -0
- data/examples/{schema.yaml → operators/schema.yaml} +0 -0
- data/examples/{sort.alf → operators/sort.alf} +0 -0
- data/examples/{summarize.alf → operators/summarize.alf} +0 -0
- data/examples/{suppliers.rash → operators/suppliers.rash} +0 -0
- data/examples/{supplies.rash → operators/supplies.rash} +0 -0
- data/examples/{ungroup.alf → operators/ungroup.alf} +0 -0
- data/examples/{union.alf → operators/union.alf} +0 -0
- data/examples/{unwrap.alf → operators/unwrap.alf} +0 -0
- data/examples/{wrap.alf → operators/wrap.alf} +0 -0
- data/lib/alf.rb +837 -62
- data/lib/alf/loader.rb +2 -1
- data/lib/alf/text.rb +160 -0
- data/lib/alf/version.rb +1 -1
- data/lib/alf/yaml.rb +24 -0
- data/spec/integration/__database__/group.alf +3 -0
- data/spec/integration/__database__/parts.rash +6 -0
- data/spec/integration/__database__/suppliers.rash +5 -0
- data/spec/integration/__database__/supplies.rash +12 -0
- data/spec/integration/command/alf/alf_e.cmd +1 -0
- data/spec/integration/command/alf/alf_e.stdout +4 -0
- data/spec/integration/command/alf/alf_env.cmd +1 -0
- data/spec/integration/command/alf/alf_env.stdout +5 -0
- data/spec/integration/command/alf/alf_implicit.alf +1 -0
- data/spec/integration/command/alf/alf_implicit_exec.cmd +1 -0
- data/spec/integration/command/alf/alf_implicit_exec.stdout +4 -0
- data/spec/integration/command/alf/alf_r.cmd +1 -0
- data/spec/integration/command/alf/alf_r.stdout +5 -0
- data/spec/integration/command/alf/alf_version.cmd +1 -0
- data/spec/integration/command/alf/alf_version.stdout +2 -0
- data/spec/integration/command/alf/alf_yaml.cmd +1 -0
- data/spec/integration/command/alf/alf_yaml.stdout +22 -0
- data/spec/integration/command/alf/rel.rash +1 -0
- data/spec/integration/command/autonum/autonum_0.cmd +1 -0
- data/spec/integration/command/autonum/autonum_0.stdout +9 -0
- data/spec/integration/command/autonum/autonum_1.cmd +1 -0
- data/spec/integration/command/autonum/autonum_1.stdout +9 -0
- data/spec/integration/command/clip/clip_0.cmd +1 -0
- data/spec/integration/command/clip/clip_0.stdout +9 -0
- data/spec/integration/command/clip/clip_1.cmd +1 -0
- data/spec/integration/command/clip/clip_1.stdout +9 -0
- data/spec/integration/command/compact/compact_0.cmd +1 -0
- data/spec/integration/command/compact/compact_0.stdout +9 -0
- data/spec/integration/command/defaults/defaults_0.cmd +1 -0
- data/spec/integration/command/defaults/defaults_0.stdout +9 -0
- data/spec/integration/command/defaults/defaults_1.cmd +1 -0
- data/spec/integration/command/defaults/defaults_1.stdout +9 -0
- data/spec/integration/command/extend/extend_0.cmd +1 -0
- data/spec/integration/command/extend/extend_0.stdout +16 -0
- data/spec/integration/command/group/group_0.cmd +1 -0
- data/spec/integration/command/group/group_0.stdout +32 -0
- data/spec/integration/command/group/group_1.cmd +1 -0
- data/spec/integration/command/group/group_1.stdout +32 -0
- data/spec/integration/command/intersect/intersect_0.cmd +1 -0
- data/spec/integration/command/intersect/intersect_0.stdout +9 -0
- data/spec/integration/command/join/join_0.cmd +1 -0
- data/spec/integration/command/join/join_0.stdout +16 -0
- data/spec/integration/command/matching/matching_0.cmd +1 -0
- data/spec/integration/command/matching/matching_0.stdout +8 -0
- data/spec/integration/command/minus/minus_0.cmd +1 -0
- data/spec/integration/command/minus/minus_0.stdout +4 -0
- data/spec/integration/command/not-matching/not-matching_0.cmd +1 -0
- data/spec/integration/command/not-matching/not-matching_0.stdout +5 -0
- data/spec/integration/command/project/project_0.cmd +1 -0
- data/spec/integration/command/project/project_0.stdout +9 -0
- data/spec/integration/command/project/project_1.cmd +1 -0
- data/spec/integration/command/project/project_1.stdout +9 -0
- data/spec/integration/command/quota/quota_0.cmd +1 -0
- data/spec/integration/command/quota/quota_0.stdout +16 -0
- data/spec/integration/command/rank/rank_1.cmd +1 -0
- data/spec/integration/command/rank/rank_1.stdout +10 -0
- data/spec/integration/command/rank/rank_2.cmd +1 -0
- data/spec/integration/command/rank/rank_2.stdout +10 -0
- data/spec/integration/command/rank/rank_3.cmd +1 -0
- data/spec/integration/command/rank/rank_3.stdout +10 -0
- data/spec/integration/command/rank/rank_4.cmd +1 -0
- data/spec/integration/command/rank/rank_4.stdout +6 -0
- data/spec/integration/command/rank/rank_5.cmd +1 -0
- data/spec/integration/command/rank/rank_5.stdout +6 -0
- data/spec/integration/command/rename/rename_0.cmd +1 -0
- data/spec/integration/command/rename/rename_0.stdout +9 -0
- data/spec/integration/command/restrict/restrict_0.cmd +1 -0
- data/spec/integration/command/restrict/restrict_0.stdout +6 -0
- data/spec/integration/command/restrict/restrict_1.cmd +1 -0
- data/spec/integration/command/restrict/restrict_1.stdout +6 -0
- data/spec/integration/command/show/show_base.cmd +1 -0
- data/spec/integration/command/show/show_base.stdout +9 -0
- data/spec/integration/command/show/show_conflictual.cmd +1 -0
- data/spec/integration/command/show/show_conflictual.stdout +5 -0
- data/spec/integration/command/show/show_rash.cmd +1 -0
- data/spec/integration/command/show/show_rash.stdout +5 -0
- data/spec/integration/command/show/show_rash_2.cmd +1 -0
- data/spec/integration/command/show/show_rash_2.stdout +5 -0
- data/spec/integration/command/show/show_yaml.cmd +1 -0
- data/spec/integration/command/show/show_yaml.stdout +22 -0
- data/spec/integration/command/sort/sort_0.cmd +1 -0
- data/spec/integration/command/sort/sort_0.stdout +9 -0
- data/spec/integration/command/sort/sort_1.cmd +1 -0
- data/spec/integration/command/sort/sort_1.stdout +9 -0
- data/spec/integration/command/summarize/summarize_0.cmd +1 -0
- data/spec/integration/command/summarize/summarize_0.stdout +8 -0
- data/spec/integration/command/ungroup/ungroup_0.cmd +1 -0
- data/spec/integration/command/ungroup/ungroup_0.stdout +16 -0
- data/spec/integration/command/union/union_0.cmd +1 -0
- data/spec/integration/command/union/union_0.stdout +9 -0
- data/spec/integration/command/unwrap/unwrap_0.cmd +1 -0
- data/spec/integration/command/unwrap/unwrap_0.stdout +9 -0
- data/spec/integration/command/wrap/wrap_0.cmd +1 -0
- data/spec/integration/command/wrap/wrap_0.stdout +9 -0
- data/spec/integration/semantics/test_join.alf +9 -0
- data/spec/integration/{src → semantics}/test_minus.alf +0 -0
- data/spec/integration/{src → semantics}/test_project.alf +0 -0
- data/spec/integration/semantics/test_rank.alf +34 -0
- data/spec/integration/test_command.rb +36 -0
- data/spec/integration/test_examples.rb +11 -22
- data/spec/integration/test_semantics.rb +40 -0
- data/spec/regression/alf_file/__FILE__.alf +2 -0
- data/spec/regression/alf_file/suppliers.rash +5 -0
- data/spec/regression/alf_file/test___FILE__.rb +17 -0
- data/spec/regression/heading/test_heading_with_date.rb +12 -0
- data/spec/regression/lispy/test_compile.rb +14 -0
- data/spec/regression/relation/test_relation_with_date.rb +12 -0
- data/spec/regression/restrict/test_restrict_with_keywords.rb +17 -0
- data/spec/shared/a_value.rb +12 -0
- data/spec/shared/an_operator_class.rb +35 -0
- data/spec/spec_helper.rb +12 -34
- data/spec/unit/assumptions/test_file.rb +17 -0
- data/spec/unit/{test_assumptions.rb → assumptions/test_instance_eval.rb} +1 -1
- data/spec/unit/assumptions/test_scoping.rb +29 -0
- data/spec/unit/environment/test_folder.rb +6 -1
- data/spec/unit/operator/relational/matching/test_hash_based.rb +60 -0
- data/spec/unit/operator/relational/not_matching/test_hash_based.rb +37 -0
- data/spec/unit/operator/relational/test_rank.rb +50 -0
- data/spec/unit/operator/test_relational.rb +3 -0
- data/spec/unit/reader/test_alf_file.rb +7 -4
- data/spec/unit/reader/test_initialize.rb +60 -0
- data/spec/unit/relation/test_relops.rb +4 -0
- data/spec/unit/relation/test_to_a.rb +41 -0
- data/spec/unit/renderer/test_initialize.rb +60 -0
- data/spec/unit/test_environment.rb +38 -0
- data/spec/unit/test_heading.rb +38 -0
- data/spec/unit/test_reader.rb +5 -0
- data/spec/unit/test_relation.rb +31 -1
- data/spec/unit/test_renderer.rb +1 -1
- data/spec/unit/{renderer/text → text}/test_cell.rb +1 -1
- data/spec/unit/{renderer/text → text}/test_row.rb +1 -1
- data/spec/unit/{renderer/text → text}/test_table.rb +1 -1
- data/spec/unit/tools/test_ordering_key.rb +13 -0
- data/spec/unit/tools/test_parse_commandline_args.rb +47 -0
- data/spec/unit/tools/test_tuple_handle.rb +34 -2
- data/spec/unit/tools/test_varargs.rb +16 -0
- data/tasks/{spec_test.rake → integration_test.rake} +4 -32
- data/tasks/regression_test.rake +52 -0
- data/tasks/unit_test.rake +33 -58
- metadata +326 -66
- data/examples/runall.sh +0 -26
- data/lib/alf/relation.rb +0 -118
- data/lib/alf/renderer/text.rb +0 -153
- data/lib/alf/renderer/yaml.rb +0 -22
- data/spec/integration/test_alf_specs.rb +0 -37
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,92 @@
|
|
1
|
+
# 0.9.3 / FIX ME
|
2
|
+
|
3
|
+
* New operators (available both in shell and in Lispy DSL)
|
4
|
+
|
5
|
+
* Added MATCHING and NOT MATCHING operators. These operators are useful
|
6
|
+
shortcuts for the following expressions.
|
7
|
+
|
8
|
+
(matching l, r) := (project (join l, r), [l's attributes])
|
9
|
+
(not_matching l, r) := (minus l, (matching l, r))
|
10
|
+
|
11
|
+
For example:
|
12
|
+
|
13
|
+
# Give suppliers that supply at least one part
|
14
|
+
(matching suppliers, supplies)
|
15
|
+
|
16
|
+
# Give suppliers that don't supply any part
|
17
|
+
(not_matching suppliers, supplies)
|
18
|
+
|
19
|
+
* Added RANK operator. The RANK operator is useful for for computing quota
|
20
|
+
queries, as shown below. See 'alf help rank' for details.
|
21
|
+
|
22
|
+
# Give the three heaviest parts
|
23
|
+
(allbut (restrict (rank :parts, [[:weight, :desc]], :pos), lambda{ pos < 3 }), [:pos])
|
24
|
+
|
25
|
+
* Enhancements when using Alf in shell
|
26
|
+
|
27
|
+
* added alf's -r option, that mimics ruby's one (require library before run)
|
28
|
+
|
29
|
+
* When alf is invoked in shell using bin/alf (and only in this case),
|
30
|
+
ENV['ALF_OPTS'] is used a global options to apply as they were specified
|
31
|
+
inline:
|
32
|
+
|
33
|
+
% export ALF_OPTS="--env=. --yaml"
|
34
|
+
% alf show suppliers
|
35
|
+
|
36
|
+
is the same as
|
37
|
+
|
38
|
+
% alf --env=. --yaml show suppliers
|
39
|
+
|
40
|
+
* 'alf --help' now distinguish experimental operators from those coming from
|
41
|
+
the (much more stable) TUTORIAL D specification. The former should be used
|
42
|
+
with care as they specification may change at any time.
|
43
|
+
|
44
|
+
* Enhancements when using Alf in Ruby
|
45
|
+
|
46
|
+
* Alf.lispy now accepts any argument recognized by Environment.autodetect; it
|
47
|
+
obtains its working Environment this way. Among others:
|
48
|
+
|
49
|
+
Alf.lispy(Alf::Environment.folder("path/to/an/existing/folder"))
|
50
|
+
|
51
|
+
is the same as:
|
52
|
+
|
53
|
+
Alf.lispy("path/to/an/existing/folder")
|
54
|
+
|
55
|
+
* Added Relation::DUM and Relation::DEE constants (relations of empty heading
|
56
|
+
with no and one tuple, respectively). They are also available as DUM and DEE
|
57
|
+
in Lispy functional expressions.
|
58
|
+
|
59
|
+
* Added a Heading abstraction, as a set of attribute (name, type) pairs
|
60
|
+
|
61
|
+
* Internal enhancements (extension points)
|
62
|
+
|
63
|
+
* The Reader and Renderer classes now accept a Hash of options as third
|
64
|
+
argument of the constructor (friendly varargs applies there). These options
|
65
|
+
can be used by extension points.
|
66
|
+
|
67
|
+
* The Environment class now provides a class-based registering mechanism 'ala'
|
68
|
+
Reader and Renderer. This allows auto-detecting the target environment when
|
69
|
+
--env=... is used in shell. See Environment.autodetect and
|
70
|
+
Environment#recognizes? for contributing to this extension point.
|
71
|
+
|
72
|
+
* Internals now rely on Myrrha for code generation. This means that all
|
73
|
+
datatypes can now be safely used in relations and dumped to .rash files in
|
74
|
+
particular.
|
75
|
+
|
76
|
+
* Bug fixes
|
77
|
+
|
78
|
+
* Added Relation#allbut, forgotten in two previous releases
|
79
|
+
* Fixed (join xxx, DEE) and (join xxx, DUM)
|
80
|
+
* Fixed scoping bug when using attributes named :path, :expr or :block in
|
81
|
+
Lispy compiled expressions (coming from .alf files)
|
82
|
+
* Fixed 'alf --yaml show suppliers' that renderer a --text table instead of
|
83
|
+
a yaml output
|
84
|
+
* Fixed bugs when using Date and Time attributes with .rash files
|
85
|
+
* Fixed bugs when using Date and Time attributes in restrict expressions
|
86
|
+
compiled from the commandline
|
87
|
+
* Fixed a few bugs when using attribute names that are ruby keywords
|
88
|
+
(restrict & extend)
|
89
|
+
|
1
90
|
# 0.9.2 / 2011.07.13
|
2
91
|
|
3
92
|
* Bug fixes
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
alf (0.9.
|
4
|
+
alf (0.9.3)
|
5
|
+
myrrha (~> 1.0.0)
|
5
6
|
quickl (~> 0.2.2)
|
6
7
|
|
7
8
|
GEM
|
@@ -10,12 +11,15 @@ GEM
|
|
10
11
|
bluecloth (2.0.11)
|
11
12
|
diff-lcs (1.1.2)
|
12
13
|
highline (1.6.2)
|
14
|
+
myrrha (1.0.0)
|
13
15
|
noe (1.3.0)
|
14
16
|
highline (~> 1.6.0)
|
15
17
|
quickl (~> 0.2.0)
|
16
18
|
wlang (~> 0.10.1)
|
17
19
|
quickl (0.2.2)
|
18
20
|
rake (0.9.2)
|
21
|
+
rcov (0.9.9)
|
22
|
+
rcov (0.9.9-java)
|
19
23
|
rspec (2.6.0)
|
20
24
|
rspec-core (~> 2.6.0)
|
21
25
|
rspec-expectations (~> 2.6.0)
|
@@ -37,6 +41,7 @@ DEPENDENCIES
|
|
37
41
|
bundler (~> 1.0)
|
38
42
|
noe (~> 1.3.0)
|
39
43
|
rake (~> 0.9.2)
|
44
|
+
rcov (~> 0.9.9)
|
40
45
|
rspec (~> 2.6.0)
|
41
46
|
wlang (~> 0.10.1)
|
42
47
|
yard (~> 0.7.2)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Alf - Relational Algebra at your fingertips (version 0.9.
|
1
|
+
# Alf - Relational Algebra at your fingertips (version 0.9.3)
|
2
2
|
|
3
3
|
## Description
|
4
4
|
|
@@ -15,6 +15,11 @@ _relations_... Let's stop the segregation ;-)
|
|
15
15
|
% [sudo] gem install alf
|
16
16
|
% alf --help
|
17
17
|
|
18
|
+
### Bundler & Require
|
19
|
+
|
20
|
+
# API is not considered stable enough for now, please use
|
21
|
+
gem "alf", "= 0.9.2"
|
22
|
+
|
18
23
|
### Links
|
19
24
|
|
20
25
|
* {http://rubydoc.info/github/blambeau/alf/master/frames} (read this file there!)
|
@@ -31,7 +36,7 @@ of a truly relational algebra approach. Objectives behind Alf are manifold:
|
|
31
36
|
as (the physical encoding of) a relation**. See 'alf --help' for the list of
|
32
37
|
available commands and implemented relational operators.
|
33
38
|
|
34
|
-
|
39
|
+
% alf restrict suppliers -- "city == 'London'" | alf join cities
|
35
40
|
|
36
41
|
* Alf is also a 100% Ruby relational algebra implementation shipped with a simple
|
37
42
|
to use, powerful, functional DSL for compiling and evaluating relational queries.
|
@@ -40,9 +45,9 @@ of a truly relational algebra approach. Objectives behind Alf are manifold:
|
|
40
45
|
section). See 'alf --help' as well as .alf files in the examples directory
|
41
46
|
for syntactic examples.
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
Alf.lispy.evaluate {
|
49
|
+
(join (restrict :suppliers, lambda{ city == 'London' }), :cities)
|
50
|
+
}
|
46
51
|
|
47
52
|
In addition to this functional syntax, Alf comes bundled with an in-memory
|
48
53
|
Relation data structure that provides an object-oriented way of manipulating
|
@@ -71,7 +76,7 @@ of a truly relational algebra approach. Objectives behind Alf are manifold:
|
|
71
76
|
the following query for the kind of things that you'll never ever have in SQL
|
72
77
|
(see also 'alf help quota', 'alf help wrap', 'alf help group', ...):
|
73
78
|
|
74
|
-
|
79
|
+
% alf --text summarize supplies --by=sid -- total "sum(:qty)" -- which "group(:pid)"
|
75
80
|
|
76
81
|
* Last, but not least, Alf is an attempt to help me test some research ideas and
|
77
82
|
communicate about them with people that already know (all or part) of the TTM
|
@@ -82,7 +87,7 @@ of a truly relational algebra approach. Objectives behind Alf are manifold:
|
|
82
87
|
'research work in progress', and used with care because not necessarily in
|
83
88
|
conformance with the TTM.
|
84
89
|
|
85
|
-
|
90
|
+
% alf --text quota supplies --by=sid --order=qty -- pos "count()"
|
86
91
|
|
87
92
|
## Overview of relational theory
|
88
93
|
|
@@ -695,8 +700,7 @@ An environment built that way will look for .rash and .alf files in the specifie
|
|
695
700
|
folder and sub-folders. I'll of course strongly consider any contribution
|
696
701
|
implementing the Environment contract on top of SQL or NoSQL databases or anything
|
697
702
|
that can be useful to manipulate with relational algebra. Such contributions can
|
698
|
-
be added to the project directly
|
699
|
-
A base template would look like:
|
703
|
+
be added to the project directly. A base template would look like:
|
700
704
|
|
701
705
|
class Foo < Alf::Environment
|
702
706
|
|
@@ -710,6 +714,9 @@ A base template would look like:
|
|
710
714
|
|
711
715
|
end
|
712
716
|
|
717
|
+
Read more about Environment's API so as to let your environment be recognized
|
718
|
+
in shell (--env=...) on rubydoc.info
|
719
|
+
|
713
720
|
### Adding file decoders, aka Readers
|
714
721
|
|
715
722
|
Environments should not be confused with Readers (see Reader class and its
|
@@ -837,18 +844,25 @@ as my own wish list, while I would love hearing yours instead.
|
|
837
844
|
### Versioning policy
|
838
845
|
|
839
846
|
Alf respects {http://semver.org/ semantic versioning}, which means that it has
|
840
|
-
a X.Y.Z version number and follows a few rules
|
841
|
-
|
842
|
-
- The public API is made of
|
843
|
-
|
844
|
-
|
845
|
-
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
847
|
+
a X.Y.Z version number and follows a few rules.
|
848
|
+
|
849
|
+
- The public API is made of the commandline tool, the Lispy dialect and the
|
850
|
+
Relation datastructure. This API will become stable with version 1.0.0 in a
|
851
|
+
near future.
|
852
|
+
- Currently, version 1.0.0 **has not been reached**. It means that **anything
|
853
|
+
may change at any time**. Best effort will be done to upgrade Y when backward
|
854
|
+
incompatible changes occur.
|
855
|
+
- Once 1.0.0 will be reached, the following rules will be followed:
|
856
|
+
- Backward compatible bug fixes will increase Z.
|
857
|
+
- New features and enhancements that do not break backward compatibility of
|
858
|
+
the public API will increase the Y number.
|
859
|
+
- Non backward compatible changes of the public API will increase the X
|
860
|
+
number.
|
861
|
+
|
862
|
+
All classes and modules but Alf module, the Lispy DSL and Alf::Relation are part
|
863
|
+
of the private API and may change at any time. A best-effort strategy is followed
|
864
|
+
to avoid breaking internals on tiny (Z) version increases, especially extension
|
865
|
+
points like Reader and Renderer.
|
852
866
|
|
853
867
|
## Enjoy Alf!
|
854
868
|
|
data/TODO.md
CHANGED
data/alf.gemspec
CHANGED
@@ -126,11 +126,13 @@ Gem::Specification.new do |s|
|
|
126
126
|
s.add_development_dependency("rake", "~> 0.9.2")
|
127
127
|
s.add_development_dependency("bundler", "~> 1.0")
|
128
128
|
s.add_development_dependency("rspec", "~> 2.6.0")
|
129
|
+
s.add_development_dependency("rcov", "~> 0.9.9")
|
129
130
|
s.add_development_dependency("yard", "~> 0.7.2")
|
130
131
|
s.add_development_dependency("bluecloth", "~> 2.0.9")
|
131
132
|
s.add_development_dependency("wlang", "~> 0.10.1")
|
132
133
|
s.add_development_dependency("noe", "~> 1.3.0")
|
133
134
|
s.add_dependency("quickl", "~> 0.2.2")
|
135
|
+
s.add_dependency("myrrha", "~> 1.0.0")
|
134
136
|
|
135
137
|
# The version of ruby required by this gem
|
136
138
|
#
|
data/alf.noespec
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
template-info:
|
2
2
|
name: "ruby"
|
3
3
|
version: 1.3.0
|
4
|
+
manifest:
|
5
|
+
tasks/unit_test.rake:
|
6
|
+
safe-override: false
|
4
7
|
variables:
|
5
8
|
lower:
|
6
9
|
alf
|
7
10
|
upper:
|
8
11
|
Alf
|
9
12
|
version:
|
10
|
-
0.9.
|
13
|
+
0.9.3
|
11
14
|
summary: |-
|
12
15
|
Relational Algebra at your fingertips
|
13
16
|
description: |-
|
@@ -26,11 +29,10 @@ variables:
|
|
26
29
|
- {name: rake, version: "~> 0.9.2", groups: [development]}
|
27
30
|
- {name: bundler, version: "~> 1.0", groups: [development]}
|
28
31
|
- {name: rspec, version: "~> 2.6.0", groups: [development]}
|
32
|
+
- {name: rcov, version: "~> 0.9.9", groups: [development]}
|
29
33
|
- {name: yard, version: "~> 0.7.2", groups: [development]}
|
30
34
|
- {name: bluecloth, version: "~> 2.0.9", groups: [development]}
|
31
35
|
- {name: wlang, version: "~> 0.10.1", groups: [development]}
|
32
36
|
- {name: noe, version: "~> 1.3.0", groups: [development]}
|
33
37
|
- {name: quickl, version: "~> 0.2.2", groups: [runtime]}
|
34
|
-
|
35
|
-
spec_test:
|
36
|
-
pattern: "spec/**/test_*.rb"
|
38
|
+
- {name: myrrha, version: "~> 1.0.0", groups: [runtime]}
|
data/bin/alf
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
module AlfLauncher
|
3
3
|
|
4
|
+
def self.lib
|
5
|
+
File.expand_path('../../lib', __FILE__)
|
6
|
+
end
|
7
|
+
|
4
8
|
def self.load
|
5
9
|
begin
|
6
|
-
$LOAD_PATH.unshift
|
10
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
7
11
|
require "alf"
|
8
12
|
rescue LoadError => ex
|
9
13
|
require "rubygems"
|
@@ -11,20 +15,12 @@ module AlfLauncher
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def self.normalize(args)
|
15
|
-
opts = []
|
16
|
-
while !args.empty? && (args.first =~ /^\-/)
|
17
|
-
opts << args.shift
|
18
|
-
end
|
19
|
-
if args.empty? or (args.size == 1 && File.exists?(args.first))
|
20
|
-
opts << "exec"
|
21
|
-
end
|
22
|
-
opts += args
|
23
|
-
end
|
24
|
-
|
25
18
|
def self.start(argv)
|
26
19
|
load
|
27
|
-
|
20
|
+
if ENV["ALF_OPTS"]
|
21
|
+
argv = Alf::Tools::parse_commandline_args(ENV["ALF_OPTS"]) + argv
|
22
|
+
end
|
23
|
+
Alf::Command::Main.run(argv, __FILE__)
|
28
24
|
end
|
29
25
|
|
30
26
|
end # module AlfLaucher
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/alf.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
require "alf/version"
|
2
|
+
require "alf/loader"
|
3
|
+
|
1
4
|
require "enumerator"
|
2
5
|
require "stringio"
|
3
6
|
require "set"
|
4
|
-
|
5
|
-
require
|
7
|
+
|
8
|
+
require 'myrrha/to_ruby_literal'
|
9
|
+
require 'myrrha/coerce'
|
6
10
|
|
7
11
|
#
|
8
12
|
# Classy data-manipulation dressed in a DSL (+ commandline)
|
@@ -15,6 +19,72 @@ module Alf
|
|
15
19
|
module Tools
|
16
20
|
|
17
21
|
#
|
22
|
+
# Parse a string with commandline arguments and returns an array.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# parse_commandline_args("--text --size=10") # => ['--text', '--size=10']
|
27
|
+
#
|
28
|
+
def parse_commandline_args(args)
|
29
|
+
args = args.split(/\s+/)
|
30
|
+
result = []
|
31
|
+
until args.empty?
|
32
|
+
if args.first[0,1] == '"'
|
33
|
+
if args.first[-1,1] == '"'
|
34
|
+
result << args.shift[1...-1]
|
35
|
+
else
|
36
|
+
block = [ args.shift[1..-1] ]
|
37
|
+
while args.first[-1,1] != '"'
|
38
|
+
block << args.shift
|
39
|
+
end
|
40
|
+
block << args.shift[0...-1]
|
41
|
+
result << block.join(" ")
|
42
|
+
end
|
43
|
+
elsif args.first[0,1] == "'"
|
44
|
+
if args.first[-1,1] == "'"
|
45
|
+
result << args.shift[1...-1]
|
46
|
+
else
|
47
|
+
block = [ args.shift[1..-1] ]
|
48
|
+
while args.first[-1,1] != "'"
|
49
|
+
block << args.shift
|
50
|
+
end
|
51
|
+
block << args.shift[0...-1]
|
52
|
+
result << block.join(" ")
|
53
|
+
end
|
54
|
+
else
|
55
|
+
result << args.shift
|
56
|
+
end
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
# Helper to define methods with multiple signatures.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
#
|
65
|
+
# varargs([1, "hello"], [Integer, String]) # => [1, "hello"]
|
66
|
+
# varargs(["hello"], [Integer, String]) # => [nil, "hello"]
|
67
|
+
#
|
68
|
+
def varargs(args, types)
|
69
|
+
types.collect{|t| t===args.first ? args.shift : nil}
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Attempt to require(who) the most friendly way as possible.
|
74
|
+
#
|
75
|
+
def friendly_require(who, dep = nil, retried = false)
|
76
|
+
gem(who, dep) if dep && defined?(Gem)
|
77
|
+
require who
|
78
|
+
rescue LoadError => ex
|
79
|
+
if retried
|
80
|
+
raise "Unable to require #{who}, which is now needed\n"\
|
81
|
+
"Try 'gem install #{who}'"
|
82
|
+
else
|
83
|
+
require 'rubygems' unless defined?(Gem)
|
84
|
+
friendly_require(who, dep, true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
18
88
|
# Returns the unqualified name of a ruby class or module
|
19
89
|
#
|
20
90
|
# Example
|
@@ -99,9 +169,8 @@ module Alf
|
|
99
169
|
if expr.empty?
|
100
170
|
compile(nil)
|
101
171
|
else
|
102
|
-
|
103
|
-
|
104
|
-
"(#{k} == #{v.inspect})"
|
172
|
+
compile expr.each_pair.collect{|k,v|
|
173
|
+
"(self.#{k} == #{Myrrha.to_ruby_literal(v)})"
|
105
174
|
}.join(" && ")
|
106
175
|
end
|
107
176
|
when Array
|
@@ -207,13 +276,31 @@ module Alf
|
|
207
276
|
@sorter = nil
|
208
277
|
end
|
209
278
|
|
279
|
+
#
|
280
|
+
# Coerces `arg` to an ordering key.
|
281
|
+
#
|
282
|
+
# Implemented coercions are:
|
283
|
+
# * Array of symbols (all attributes in ascending order)
|
284
|
+
# * Array of [Symbol, :asc|:desc] pairs (obvious semantics)
|
285
|
+
# * ProjectionKey (all its attributes in ascending order)
|
286
|
+
# * OrderingKey (self)
|
287
|
+
#
|
288
|
+
# @return [OrderingKey]
|
289
|
+
# @raises [ArgumentError] when `arg` is not recognized
|
290
|
+
#
|
210
291
|
def self.coerce(arg)
|
211
292
|
case arg
|
212
293
|
when Array
|
213
|
-
if arg.all?{|a| a.is_a?(
|
214
|
-
arg
|
294
|
+
if arg.all?{|a| a.is_a?(Array)}
|
295
|
+
OrderingKey.new(arg)
|
296
|
+
elsif arg.all?{|a| a.is_a?(Symbol)}
|
297
|
+
sliced = arg.each_slice(2)
|
298
|
+
if sliced.all?{|a,o| [:asc,:desc].include?(o)}
|
299
|
+
OrderingKey.new sliced.to_a
|
300
|
+
else
|
301
|
+
OrderingKey.new arg.collect{|a| [a, :asc]}
|
302
|
+
end
|
215
303
|
end
|
216
|
-
OrderingKey.new(arg)
|
217
304
|
when ProjectionKey
|
218
305
|
arg.to_ordering_key
|
219
306
|
when OrderingKey
|
@@ -261,26 +348,6 @@ module Alf
|
|
261
348
|
extend Tools
|
262
349
|
end # module Tools
|
263
350
|
|
264
|
-
#
|
265
|
-
# Builds and returns a lispy engine on a specific environment.
|
266
|
-
#
|
267
|
-
# Example(s):
|
268
|
-
#
|
269
|
-
# # Returns a lispy instance on the default environment
|
270
|
-
# lispy = Alf.lispy
|
271
|
-
#
|
272
|
-
# # Returns a lispy instance on the examples' environment
|
273
|
-
# lispy = Alf.lispy(Alf::Environment.examples)
|
274
|
-
#
|
275
|
-
# # Returns a lispy instance on a folder environment of your choice
|
276
|
-
# lispy = Alf.lispy(Alf::Environment.folder('path/to/a/folder'))
|
277
|
-
#
|
278
|
-
# @see Alf::Environment about available environments and their contract
|
279
|
-
#
|
280
|
-
def self.lispy(env = Alf::Environment.default)
|
281
|
-
Command::Main.new(env)
|
282
|
-
end
|
283
|
-
|
284
351
|
#
|
285
352
|
# Encapsulates the interface with the outside world, providing base iterators
|
286
353
|
# for named datasets, among others.
|
@@ -301,9 +368,81 @@ module Alf
|
|
301
368
|
# You can implement your own environment by subclassing this class and
|
302
369
|
# implementing the {#dataset} method. As additional support is implemented
|
303
370
|
# in the base class, Environment should never be mimiced.
|
371
|
+
#
|
372
|
+
# This class provides an extension point allowing to participate to auto
|
373
|
+
# detection and resolving of the --env=... option when alf is used in shell.
|
374
|
+
# See Environment.register, Environment.autodetect and Environment.recognizes?
|
375
|
+
# for details.
|
304
376
|
#
|
305
377
|
class Environment
|
306
378
|
|
379
|
+
# Registered environments
|
380
|
+
@@environments = []
|
381
|
+
|
382
|
+
#
|
383
|
+
# Register an environment class under a specific name.
|
384
|
+
#
|
385
|
+
# Registered class must implement a recognizes? method that takes an array
|
386
|
+
# of arguments; it must returns true if an environment instance can be built
|
387
|
+
# using those arguments, false otherwise. Please be very specific in the
|
388
|
+
# implementation for returning true. See also autodetect and recognizes?
|
389
|
+
#
|
390
|
+
# @param [Symbol] name name of the environment kind
|
391
|
+
# @param [Class] clazz class that implemented the environment
|
392
|
+
#
|
393
|
+
def self.register(name, clazz)
|
394
|
+
@@environments << [name, clazz]
|
395
|
+
(class << self; self; end).
|
396
|
+
send(:define_method, name) do |*args|
|
397
|
+
clazz.new(*args)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
#
|
402
|
+
# Auto-detect the environment to use for specific arguments.
|
403
|
+
#
|
404
|
+
# This method returns an instance of the first registered Environment class
|
405
|
+
# that returns true to an invocation of recognizes?(args). It raises an
|
406
|
+
# ArgumentError if no such class can be found.
|
407
|
+
#
|
408
|
+
# @return [Environment] an environment instance
|
409
|
+
# @raise [ArgumentError] when no registered class recognizes the arguments
|
410
|
+
#
|
411
|
+
def self.autodetect(*args)
|
412
|
+
if (args.size == 1) && args.first.is_a?(Environment)
|
413
|
+
return args.first
|
414
|
+
else
|
415
|
+
@@environments.each do |name,clazz|
|
416
|
+
return clazz.new(*args) if clazz.recognizes?(args)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
raise ArgumentError, "Unable to auto-detect Environment with #{args.inspect}"
|
420
|
+
end
|
421
|
+
|
422
|
+
#
|
423
|
+
# (see Environment.autodetect)
|
424
|
+
#
|
425
|
+
def self.coerce(*args)
|
426
|
+
autodetect(*args)
|
427
|
+
end
|
428
|
+
|
429
|
+
#
|
430
|
+
# Returns true _args_ can be used for building an environment instance,
|
431
|
+
# false otherwise.
|
432
|
+
#
|
433
|
+
# When returning true, an immediate invocation of new(*args) should
|
434
|
+
# succeed. While runtime exception are admitted (no such database, for
|
435
|
+
# example), argument errors should not occur (missing argument, wrong
|
436
|
+
# typing, etc.).
|
437
|
+
#
|
438
|
+
# Please be specific in the implementation of this extension point, as
|
439
|
+
# registered environments for a chain and each of them should have a
|
440
|
+
# chance of being selected.
|
441
|
+
#
|
442
|
+
def self.recognizes?(args)
|
443
|
+
false
|
444
|
+
end
|
445
|
+
|
307
446
|
#
|
308
447
|
# Returns a dataset whose name is provided.
|
309
448
|
#
|
@@ -378,6 +517,18 @@ module Alf
|
|
378
517
|
#
|
379
518
|
class Folder < Environment
|
380
519
|
|
520
|
+
#
|
521
|
+
# (see Environment.recognizes?)
|
522
|
+
#
|
523
|
+
# Returns true if args contains onely a String which is an existing
|
524
|
+
# folder.
|
525
|
+
#
|
526
|
+
def self.recognizes?(args)
|
527
|
+
(args.size == 1) &&
|
528
|
+
args.first.is_a?(String) &&
|
529
|
+
File.directory?(args.first.to_s)
|
530
|
+
end
|
531
|
+
|
381
532
|
#
|
382
533
|
# Creates an environment instance, wired to the specified folder.
|
383
534
|
#
|
@@ -412,15 +563,9 @@ module Alf
|
|
412
563
|
end
|
413
564
|
end
|
414
565
|
|
566
|
+
Environment.register(:folder, self)
|
415
567
|
end # class Folder
|
416
568
|
|
417
|
-
#
|
418
|
-
# Factors a Folder environment on a specific path
|
419
|
-
#
|
420
|
-
def self.folder(path)
|
421
|
-
Folder.new(path)
|
422
|
-
end
|
423
|
-
|
424
569
|
#
|
425
570
|
# Returns the default environment
|
426
571
|
#
|
@@ -432,7 +577,7 @@ module Alf
|
|
432
577
|
# Returns the examples environment
|
433
578
|
#
|
434
579
|
def self.examples
|
435
|
-
folder File.expand_path('../../examples', __FILE__)
|
580
|
+
folder File.expand_path('../../examples/operators', __FILE__)
|
436
581
|
end
|
437
582
|
|
438
583
|
end # class Environment
|
@@ -572,8 +717,10 @@ module Alf
|
|
572
717
|
else
|
573
718
|
raise "No registered reader for #{ext} (#{filepath})"
|
574
719
|
end
|
575
|
-
|
720
|
+
elsif args.empty?
|
576
721
|
coerce(filepath)
|
722
|
+
else
|
723
|
+
raise ArgumentError, "Unable to return a reader for #{filepath} and #{args}"
|
577
724
|
end
|
578
725
|
end
|
579
726
|
|
@@ -603,21 +750,33 @@ module Alf
|
|
603
750
|
end
|
604
751
|
end
|
605
752
|
|
753
|
+
# Default reader options
|
754
|
+
DEFAULT_OPTIONS = {}
|
755
|
+
|
606
756
|
# @return [Environment] Wired environment
|
607
757
|
attr_accessor :environment
|
608
758
|
|
609
759
|
# @return [String or IO] Input IO, or file name
|
610
760
|
attr_accessor :input
|
761
|
+
|
762
|
+
# @return [Hash] Reader's options
|
763
|
+
attr_accessor :options
|
611
764
|
|
612
765
|
#
|
613
|
-
# Creates a reader instance
|
766
|
+
# Creates a reader instance.
|
614
767
|
#
|
615
768
|
# @param [String or IO] path to a file or IO object for input
|
616
769
|
# @param [Environment] environment wired environment, serving this reader
|
770
|
+
# @param [Hash] options Reader's options (see doc of subclasses)
|
617
771
|
#
|
618
|
-
def initialize(
|
619
|
-
@input =
|
620
|
-
|
772
|
+
def initialize(*args)
|
773
|
+
@input, @environment, @options = case args.first
|
774
|
+
when String, IO, StringIO
|
775
|
+
Tools.varargs(args, [args.first.class, Environment, Hash])
|
776
|
+
else
|
777
|
+
Tools.varargs(args, [String, Environment, Hash])
|
778
|
+
end
|
779
|
+
@options = self.class.const_get(:DEFAULT_OPTIONS).merge(@options || {})
|
621
780
|
end
|
622
781
|
|
623
782
|
#
|
@@ -821,17 +980,33 @@ module Alf
|
|
821
980
|
@@renderers.each(&Proc.new)
|
822
981
|
end
|
823
982
|
|
983
|
+
# Default renderer options
|
984
|
+
DEFAULT_OPTIONS = {}
|
985
|
+
|
824
986
|
# Renderer input (typically an Iterator)
|
825
987
|
attr_accessor :input
|
826
988
|
|
827
989
|
# @return [Environment] Optional wired environment
|
828
990
|
attr_accessor :environment
|
829
991
|
|
992
|
+
# @return [Hash] Renderer's options
|
993
|
+
attr_accessor :options
|
994
|
+
|
830
995
|
#
|
831
|
-
# Creates a
|
996
|
+
# Creates a reader instance.
|
832
997
|
#
|
833
|
-
|
834
|
-
|
998
|
+
# @param [Iterator] iterator an Iterator of tuples to render
|
999
|
+
# @param [Environment] environment wired environment, serving this reader
|
1000
|
+
# @param [Hash] options Reader's options (see doc of subclasses)
|
1001
|
+
#
|
1002
|
+
def initialize(*args)
|
1003
|
+
@input, @environment, @options = case args.first
|
1004
|
+
when Array
|
1005
|
+
Tools.varargs(args, [Array, Environment, Hash])
|
1006
|
+
else
|
1007
|
+
Tools.varargs(args, [Iterator, Environment, Hash])
|
1008
|
+
end
|
1009
|
+
@options = self.class.const_get(:DEFAULT_OPTIONS).merge(@options || {})
|
835
1010
|
end
|
836
1011
|
|
837
1012
|
#
|
@@ -877,7 +1052,7 @@ module Alf
|
|
877
1052
|
# (see Renderer#render)
|
878
1053
|
def render(input, output)
|
879
1054
|
input.each do |tuple|
|
880
|
-
output << tuple
|
1055
|
+
output << Myrrha.to_ruby_literal(tuple) << "\n"
|
881
1056
|
end
|
882
1057
|
output
|
883
1058
|
end
|
@@ -885,8 +1060,6 @@ module Alf
|
|
885
1060
|
Renderer.register(:rash, "as ruby hashes", self)
|
886
1061
|
end # class Rash
|
887
1062
|
|
888
|
-
require "alf/renderer/text"
|
889
|
-
require "alf/renderer/yaml"
|
890
1063
|
end # module Renderer
|
891
1064
|
|
892
1065
|
#
|
@@ -937,7 +1110,14 @@ module Alf
|
|
937
1110
|
#
|
938
1111
|
# RELATIONAL COMMANDS
|
939
1112
|
# #{summarized_subcommands subcommands.select{|cmd|
|
940
|
-
# cmd.include?(Alf::Operator::Relational)
|
1113
|
+
# cmd.include?(Alf::Operator::Relational) &&
|
1114
|
+
# !cmd.include?(Alf::Operator::Experimental)
|
1115
|
+
# }}
|
1116
|
+
#
|
1117
|
+
# EXPERIMENTAL OPERATORS
|
1118
|
+
# #{summarized_subcommands subcommands.select{|cmd|
|
1119
|
+
# cmd.include?(Alf::Operator::Relational) &&
|
1120
|
+
# cmd.include?(Alf::Operator::Experimental)
|
941
1121
|
# }}
|
942
1122
|
#
|
943
1123
|
# NON-RELATIONAL COMMANDS
|
@@ -964,7 +1144,6 @@ module Alf
|
|
964
1144
|
# Creates a command instance
|
965
1145
|
def initialize(env = Environment.default)
|
966
1146
|
@environment = env
|
967
|
-
extend(Lispy)
|
968
1147
|
end
|
969
1148
|
|
970
1149
|
# Install options
|
@@ -974,16 +1153,20 @@ module Alf
|
|
974
1153
|
@execute = true
|
975
1154
|
end
|
976
1155
|
|
977
|
-
@renderer =
|
1156
|
+
@renderer = nil
|
978
1157
|
Renderer.each_renderer do |name,descr,clazz|
|
979
1158
|
opt.on("--#{name}", "Render output #{descr}"){
|
980
1159
|
@renderer = clazz.new
|
981
1160
|
}
|
982
1161
|
end
|
983
1162
|
|
984
|
-
opt.on('--env=
|
985
|
-
"Set the environment
|
986
|
-
@environment = Environment.
|
1163
|
+
opt.on('--env=ENV',
|
1164
|
+
"Set the environment to use") do |value|
|
1165
|
+
@environment = Environment.autodetect(value)
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
opt.on('-rlibrary', "require the library, before executing alf") do |value|
|
1169
|
+
require(value)
|
987
1170
|
end
|
988
1171
|
|
989
1172
|
opt.on_tail('-h', "--help", "Show help") do
|
@@ -991,16 +1174,29 @@ module Alf
|
|
991
1174
|
end
|
992
1175
|
|
993
1176
|
opt.on_tail('-v', "--version", "Show version") do
|
994
|
-
raise Quickl::Exit, "
|
1177
|
+
raise Quickl::Exit, "alf #{Alf::VERSION}"\
|
995
1178
|
" (c) 2011, Bernard Lambeau"
|
996
1179
|
end
|
997
1180
|
end # Alf's options
|
998
1181
|
|
1182
|
+
#
|
1183
|
+
def _normalize(args)
|
1184
|
+
opts = []
|
1185
|
+
while !args.empty? && (args.first =~ /^\-/)
|
1186
|
+
opts << args.shift
|
1187
|
+
end
|
1188
|
+
if args.empty? or (args.size == 1 && File.exists?(args.first))
|
1189
|
+
opts << "exec"
|
1190
|
+
end
|
1191
|
+
opts += args
|
1192
|
+
end
|
1193
|
+
|
999
1194
|
#
|
1000
1195
|
# Overrided because Quickl only keep --options but modifying it there
|
1001
1196
|
# should probably be considered a broken API.
|
1002
1197
|
#
|
1003
1198
|
def _run(argv = [])
|
1199
|
+
argv = _normalize(argv)
|
1004
1200
|
|
1005
1201
|
# 1) Extract my options and parse them
|
1006
1202
|
my_argv = []
|
@@ -1011,7 +1207,7 @@ module Alf
|
|
1011
1207
|
|
1012
1208
|
# 2) build the operator according to -e option
|
1013
1209
|
operator = if @execute
|
1014
|
-
|
1210
|
+
Alf.lispy(environment).compile(argv.first)
|
1015
1211
|
else
|
1016
1212
|
super
|
1017
1213
|
end
|
@@ -1019,6 +1215,7 @@ module Alf
|
|
1019
1215
|
# 3) if there is a requester, then we do the job (assuming bin/alf)
|
1020
1216
|
# with the renderer to use. Otherwise, we simply return built operator
|
1021
1217
|
if operator && requester
|
1218
|
+
renderer = self.renderer ||= Renderer::Rash.new
|
1022
1219
|
renderer.pipe(operator, environment).execute($stdout)
|
1023
1220
|
else
|
1024
1221
|
operator
|
@@ -1048,7 +1245,7 @@ module Alf
|
|
1048
1245
|
include Command
|
1049
1246
|
|
1050
1247
|
options do |opt|
|
1051
|
-
@renderer =
|
1248
|
+
@renderer = nil
|
1052
1249
|
Renderer.each_renderer do |name,descr,clazz|
|
1053
1250
|
opt.on("--#{name}", "Render output #{descr}"){
|
1054
1251
|
@renderer = clazz.new
|
@@ -1057,7 +1254,7 @@ module Alf
|
|
1057
1254
|
end
|
1058
1255
|
|
1059
1256
|
def execute(args)
|
1060
|
-
requester.renderer = @renderer
|
1257
|
+
requester.renderer = (@renderer || requester.renderer || Text::Renderer.new)
|
1061
1258
|
args = [ $stdin ] if args.empty?
|
1062
1259
|
args.first
|
1063
1260
|
end
|
@@ -1456,6 +1653,9 @@ module Alf
|
|
1456
1653
|
|
1457
1654
|
end # module Shortcut
|
1458
1655
|
|
1656
|
+
# Marker for experimental operators
|
1657
|
+
module Experimental; end
|
1658
|
+
|
1459
1659
|
end # module Operator
|
1460
1660
|
|
1461
1661
|
#
|
@@ -2089,40 +2289,80 @@ module Alf
|
|
2089
2289
|
class Join < Factory::Operator(__FILE__, __LINE__)
|
2090
2290
|
include Operator::Relational, Operator::Shortcut, Operator::Binary
|
2091
2291
|
|
2292
|
+
#
|
2293
|
+
# Performs a Join of two relations through a Hash buffer on the right
|
2294
|
+
# one.
|
2295
|
+
#
|
2092
2296
|
class HashBased
|
2093
2297
|
include Operator::Binary
|
2094
2298
|
|
2299
|
+
#
|
2300
|
+
# Implements a special Buffer for join-based relational operators.
|
2301
|
+
#
|
2302
|
+
# Example:
|
2303
|
+
#
|
2304
|
+
# buffer = Buffer::Join.new(...) # pass the right part of the join
|
2305
|
+
# left.each do |left_tuple|
|
2306
|
+
# key, rest = buffer.split(tuple)
|
2307
|
+
# buffer.each(key) do |right_tuple|
|
2308
|
+
# #
|
2309
|
+
# # do whatever you want with left and right tuples
|
2310
|
+
# #
|
2311
|
+
# end
|
2312
|
+
# end
|
2313
|
+
#
|
2095
2314
|
class JoinBuffer
|
2096
2315
|
|
2316
|
+
#
|
2317
|
+
# Creates a buffer instance with the right part of the join.
|
2318
|
+
#
|
2319
|
+
# @param [Iterator] enum a tuple iterator, right part of the join.
|
2320
|
+
#
|
2097
2321
|
def initialize(enum)
|
2098
2322
|
@buffer = nil
|
2099
2323
|
@key = nil
|
2100
2324
|
@enum = enum
|
2101
2325
|
end
|
2102
2326
|
|
2327
|
+
#
|
2328
|
+
# Splits a left tuple according to the common key.
|
2329
|
+
#
|
2330
|
+
# @param [Hash] tuple a left tuple of the join
|
2331
|
+
# @return [Array] an array of two elements, the key and the rest
|
2332
|
+
# @see ProjectionKey#split
|
2333
|
+
#
|
2103
2334
|
def split(tuple)
|
2104
2335
|
_init(tuple) unless @key
|
2105
2336
|
@key.split(tuple)
|
2106
2337
|
end
|
2107
2338
|
|
2339
|
+
#
|
2340
|
+
# Yields each right tuple that matches a given key value.
|
2341
|
+
#
|
2342
|
+
# @param [Hash] key a tuple that matches elements of the common key
|
2343
|
+
# (typically the first element returned by #split)
|
2344
|
+
#
|
2108
2345
|
def each(key)
|
2109
2346
|
@buffer[key].each(&Proc.new) if @buffer.has_key?(key)
|
2110
2347
|
end
|
2111
2348
|
|
2112
2349
|
private
|
2113
2350
|
|
2351
|
+
# Initialize the buffer with a right tuple
|
2114
2352
|
def _init(right)
|
2115
2353
|
@buffer = Hash.new{|h,k| h[k] = []}
|
2116
2354
|
@enum.each do |left|
|
2117
2355
|
@key = Tools::ProjectionKey.coerce(left.keys & right.keys) unless @key
|
2118
2356
|
@buffer[@key.project(left)] << left
|
2119
2357
|
end
|
2358
|
+
@key = Tools::ProjectionKey.coerce([]) unless @key
|
2120
2359
|
end
|
2121
2360
|
|
2122
|
-
end
|
2361
|
+
end # class JoinBuffer
|
2123
2362
|
|
2124
2363
|
protected
|
2125
2364
|
|
2365
|
+
# (see Operator#_each)
|
2126
2366
|
def _each
|
2127
2367
|
buffer = JoinBuffer.new(right)
|
2128
2368
|
left.each do |left_tuple|
|
@@ -2291,6 +2531,128 @@ module Alf
|
|
2291
2531
|
end
|
2292
2532
|
|
2293
2533
|
end # class Union
|
2534
|
+
|
2535
|
+
#
|
2536
|
+
# Relational matching
|
2537
|
+
#
|
2538
|
+
# SYNOPSIS
|
2539
|
+
# #{program_name} #{command_name} [LEFT] RIGHT
|
2540
|
+
#
|
2541
|
+
# API & EXAMPLE
|
2542
|
+
#
|
2543
|
+
# (matching :suppliers, :supplies)
|
2544
|
+
#
|
2545
|
+
# DESCRIPTION
|
2546
|
+
#
|
2547
|
+
# This operator restricts left tuples to those for which there exists at
|
2548
|
+
# least one right tuple that joins. This is a shortcut operator for the
|
2549
|
+
# longer expression:
|
2550
|
+
#
|
2551
|
+
# (project (join xxx, yyy), [xxx's attributes])
|
2552
|
+
#
|
2553
|
+
# In shell:
|
2554
|
+
#
|
2555
|
+
# alf matching suppliers supplies
|
2556
|
+
#
|
2557
|
+
class Matching < Factory::Operator(__FILE__, __LINE__)
|
2558
|
+
include Operator::Relational, Operator::Shortcut, Operator::Binary
|
2559
|
+
|
2560
|
+
#
|
2561
|
+
# Performs a Matching of two relations through a Hash buffer on the right
|
2562
|
+
# one.
|
2563
|
+
#
|
2564
|
+
class HashBased
|
2565
|
+
include Operator::Binary
|
2566
|
+
|
2567
|
+
# (see Operator#_each)
|
2568
|
+
def _each
|
2569
|
+
seen, key = nil, nil
|
2570
|
+
left.each do |left_tuple|
|
2571
|
+
seen ||= begin
|
2572
|
+
h = Hash.new
|
2573
|
+
right.each do |right_tuple|
|
2574
|
+
key ||= Tools::ProjectionKey.coerce(left_tuple.keys & right_tuple.keys)
|
2575
|
+
h[key.project(right_tuple)] = true
|
2576
|
+
end
|
2577
|
+
key ||= Tools::ProjectionKey.coerce([])
|
2578
|
+
h
|
2579
|
+
end
|
2580
|
+
yield(left_tuple) if seen.has_key?(key.project(left_tuple))
|
2581
|
+
end
|
2582
|
+
end
|
2583
|
+
|
2584
|
+
end # class HashBased
|
2585
|
+
|
2586
|
+
protected
|
2587
|
+
|
2588
|
+
# (see Shortcut#longexpr)
|
2589
|
+
def longexpr
|
2590
|
+
chain HashBased.new,
|
2591
|
+
datasets
|
2592
|
+
end
|
2593
|
+
|
2594
|
+
end # class Matching
|
2595
|
+
|
2596
|
+
#
|
2597
|
+
# Relational not matching
|
2598
|
+
#
|
2599
|
+
# SYNOPSIS
|
2600
|
+
# #{program_name} #{command_name} [LEFT] RIGHT
|
2601
|
+
#
|
2602
|
+
# API & EXAMPLE
|
2603
|
+
#
|
2604
|
+
# (not_matching :suppliers, :supplies)
|
2605
|
+
#
|
2606
|
+
# DESCRIPTION
|
2607
|
+
#
|
2608
|
+
# This operator restricts left tuples to those for which there does not
|
2609
|
+
# exist any right tuple that joins. This is a shortcut operator for the
|
2610
|
+
# longer expression:
|
2611
|
+
#
|
2612
|
+
# (minus xxx, (matching xxx, yyy))
|
2613
|
+
#
|
2614
|
+
# In shell:
|
2615
|
+
#
|
2616
|
+
# alf not-matching suppliers supplies
|
2617
|
+
#
|
2618
|
+
class NotMatching < Factory::Operator(__FILE__, __LINE__)
|
2619
|
+
include Operator::Relational, Operator::Shortcut, Operator::Binary
|
2620
|
+
|
2621
|
+
#
|
2622
|
+
# Performs a NotMatching of two relations through a Hash buffer on the
|
2623
|
+
# right one.
|
2624
|
+
#
|
2625
|
+
class HashBased
|
2626
|
+
include Operator::Binary
|
2627
|
+
|
2628
|
+
# (see Operator#_each)
|
2629
|
+
def _each
|
2630
|
+
seen, key = nil, nil
|
2631
|
+
left.each do |left_tuple|
|
2632
|
+
seen ||= begin
|
2633
|
+
h = Hash.new
|
2634
|
+
right.each do |right_tuple|
|
2635
|
+
key ||= Tools::ProjectionKey.coerce(left_tuple.keys & right_tuple.keys)
|
2636
|
+
h[key.project(right_tuple)] = true
|
2637
|
+
end
|
2638
|
+
key ||= Tools::ProjectionKey.coerce([])
|
2639
|
+
h
|
2640
|
+
end
|
2641
|
+
yield(left_tuple) unless seen.has_key?(key.project(left_tuple))
|
2642
|
+
end
|
2643
|
+
end
|
2644
|
+
|
2645
|
+
end # class HashBased
|
2646
|
+
|
2647
|
+
protected
|
2648
|
+
|
2649
|
+
# (see Shortcut#longexpr)
|
2650
|
+
def longexpr
|
2651
|
+
chain HashBased.new,
|
2652
|
+
datasets
|
2653
|
+
end
|
2654
|
+
|
2655
|
+
end # class NotMatching
|
2294
2656
|
|
2295
2657
|
#
|
2296
2658
|
# Relational wraping (tuple-valued attributes)
|
@@ -2670,6 +3032,117 @@ module Alf
|
|
2670
3032
|
|
2671
3033
|
end # class Summarize
|
2672
3034
|
|
3035
|
+
#
|
3036
|
+
# Relational ranking (explicit tuple positions)
|
3037
|
+
#
|
3038
|
+
# SYNOPSIS
|
3039
|
+
# #{program_name} #{command_name} [OPERAND] --order=OR1... -- [RANKNAME]
|
3040
|
+
#
|
3041
|
+
# OPTIONS
|
3042
|
+
# #{summarized_options}
|
3043
|
+
#
|
3044
|
+
# API & EXAMPLE
|
3045
|
+
#
|
3046
|
+
# # Position attribute => # of tuples with smaller weight
|
3047
|
+
# (rank :parts, [:weight], :position)
|
3048
|
+
#
|
3049
|
+
# # Position attribute => # of tuples with greater weight
|
3050
|
+
# (rank :parts, [[:weight, :desc]], :position)
|
3051
|
+
#
|
3052
|
+
# DESCRIPTION
|
3053
|
+
#
|
3054
|
+
# This operator computes the ranking of input tuples, according to an order
|
3055
|
+
# relation. Precisely, it extends the input tuples with a RANKNAME attribute
|
3056
|
+
# whose value is the number of tuples which are considered strictly less
|
3057
|
+
# according to the specified order. For the two examples above:
|
3058
|
+
#
|
3059
|
+
# alf rank parts --order=weight -- position
|
3060
|
+
# alf rank parts --order=weight,desc -- position
|
3061
|
+
#
|
3062
|
+
# Note that, unless the ordering key includes a candidate key for the input
|
3063
|
+
# relation, the newly RANKNAME attribute is not necessarily a candidate key
|
3064
|
+
# for the output one. In the example above, adding the :pid attribute
|
3065
|
+
# ensured that position will contain all different values:
|
3066
|
+
#
|
3067
|
+
# alf rank parts --order=weight,pid -- position
|
3068
|
+
#
|
3069
|
+
# Or even:
|
3070
|
+
#
|
3071
|
+
# alf rank parts --order=weight,desc,pid,asc -- position
|
3072
|
+
#
|
3073
|
+
class Rank < Factory::Operator(__FILE__, __LINE__)
|
3074
|
+
include Operator::Relational, Operator::Shortcut, Operator::Unary
|
3075
|
+
|
3076
|
+
# Ranking order
|
3077
|
+
attr_accessor :order
|
3078
|
+
|
3079
|
+
# Ranking attribute name
|
3080
|
+
attr_accessor :ranking_name
|
3081
|
+
|
3082
|
+
def initialize(order = [], ranking_name = :rank)
|
3083
|
+
@order, @ranking_name = order, ranking_name
|
3084
|
+
end
|
3085
|
+
|
3086
|
+
options do |opt|
|
3087
|
+
opt.on('--order=x,y,z', 'Specify ranking order', Array) do |args|
|
3088
|
+
@order = args.collect{|a| a.to_sym}
|
3089
|
+
end
|
3090
|
+
end
|
3091
|
+
|
3092
|
+
class SortBased
|
3093
|
+
include Operator::Cesure
|
3094
|
+
|
3095
|
+
def initialize(order, ranking_name)
|
3096
|
+
@order, @ranking_name = order, ranking_name
|
3097
|
+
end
|
3098
|
+
|
3099
|
+
def ordering_key
|
3100
|
+
OrderingKey.coerce @order
|
3101
|
+
end
|
3102
|
+
|
3103
|
+
def cesure_key
|
3104
|
+
ProjectionKey.coerce(ordering_key)
|
3105
|
+
end
|
3106
|
+
|
3107
|
+
def start_cesure(key, receiver)
|
3108
|
+
@rank ||= 0
|
3109
|
+
@last_block = 0
|
3110
|
+
end
|
3111
|
+
|
3112
|
+
def accumulate_cesure(tuple, receiver)
|
3113
|
+
receiver.call tuple.merge(@ranking_name => @rank)
|
3114
|
+
@last_block += 1
|
3115
|
+
end
|
3116
|
+
|
3117
|
+
def flush_cesure(key, receiver)
|
3118
|
+
@rank += @last_block
|
3119
|
+
end
|
3120
|
+
|
3121
|
+
end # class SortBased
|
3122
|
+
|
3123
|
+
protected
|
3124
|
+
|
3125
|
+
# (see Operator::CommandMethods#set_args)
|
3126
|
+
def set_args(args)
|
3127
|
+
unless args.empty?
|
3128
|
+
self.ranking_name = args.first.to_sym
|
3129
|
+
end
|
3130
|
+
self
|
3131
|
+
end
|
3132
|
+
|
3133
|
+
def ordering_key
|
3134
|
+
OrderingKey.coerce @order
|
3135
|
+
end
|
3136
|
+
|
3137
|
+
def longexpr
|
3138
|
+
sort_key = ordering_key
|
3139
|
+
chain SortBased.new(sort_key, @ranking_name),
|
3140
|
+
Operator::NonRelational::Sort.new(sort_key),
|
3141
|
+
datasets
|
3142
|
+
end
|
3143
|
+
|
3144
|
+
end # class Rank
|
3145
|
+
|
2673
3146
|
#
|
2674
3147
|
# Relational quota-queries (position, sum progression, etc.)
|
2675
3148
|
#
|
@@ -2692,7 +3165,8 @@ module Alf
|
|
2692
3165
|
# alf quota supplies --by=sid --order=qty -- position count sum_qty "sum(:qty)"
|
2693
3166
|
#
|
2694
3167
|
class Quota < Factory::Operator(__FILE__, __LINE__)
|
2695
|
-
include Operator::Relational, Operator::
|
3168
|
+
include Operator::Relational, Operator::Experimental,
|
3169
|
+
Operator::Shortcut, Operator::Unary
|
2696
3170
|
|
2697
3171
|
# Quota by
|
2698
3172
|
attr_accessor :by
|
@@ -2991,24 +3465,42 @@ module Alf
|
|
2991
3465
|
#
|
2992
3466
|
# Keeps tuples ordered on a specific key
|
2993
3467
|
#
|
3468
|
+
# Example:
|
3469
|
+
#
|
3470
|
+
# sorted = Buffer::Sorted.new OrderingKey.new(...)
|
3471
|
+
# sorted.add_all(...)
|
3472
|
+
# sorted.each do |tuple|
|
3473
|
+
# # tuples are ordered here
|
3474
|
+
# end
|
3475
|
+
#
|
2994
3476
|
class Sorted < Buffer
|
2995
3477
|
|
3478
|
+
#
|
3479
|
+
# Creates a buffer instance with an ordering key
|
3480
|
+
#
|
2996
3481
|
def initialize(ordering_key)
|
2997
3482
|
@ordering_key = ordering_key
|
2998
3483
|
@buffer = []
|
2999
3484
|
end
|
3000
3485
|
|
3486
|
+
#
|
3487
|
+
# Adds all elements of an iterator to the buffer
|
3488
|
+
#
|
3001
3489
|
def add_all(enum)
|
3002
3490
|
sorter = @ordering_key.sorter
|
3003
3491
|
@buffer = merge_sort(@buffer, enum.to_a.sort(&sorter), sorter)
|
3004
3492
|
end
|
3005
3493
|
|
3494
|
+
#
|
3495
|
+
# (see Buffer#each)
|
3496
|
+
#
|
3006
3497
|
def each
|
3007
3498
|
@buffer.each(&Proc.new)
|
3008
3499
|
end
|
3009
3500
|
|
3010
3501
|
private
|
3011
3502
|
|
3503
|
+
# Implements a merge sort between two iterators s1 and s2
|
3012
3504
|
def merge_sort(s1, s2, sorter)
|
3013
3505
|
(s1 + s2).sort(&sorter)
|
3014
3506
|
end
|
@@ -3017,7 +3509,245 @@ module Alf
|
|
3017
3509
|
|
3018
3510
|
end # class Buffer
|
3019
3511
|
|
3020
|
-
#
|
3512
|
+
#
|
3513
|
+
# Defines a Heading, that is, a set of attribute (name,domain) pairs.
|
3514
|
+
#
|
3515
|
+
class Heading
|
3516
|
+
|
3517
|
+
#
|
3518
|
+
# Creates a Heading instance
|
3519
|
+
#
|
3520
|
+
# @param [Hash] a hash of attribute (name, type) pairs where name is
|
3521
|
+
# a Symbol and type is a Class
|
3522
|
+
#
|
3523
|
+
def self.[](attributes)
|
3524
|
+
Heading.new(attributes)
|
3525
|
+
end
|
3526
|
+
|
3527
|
+
# @return [Hash] a (freezed) hash of (name, type) pairs
|
3528
|
+
attr_reader :attributes
|
3529
|
+
|
3530
|
+
#
|
3531
|
+
# Creates a Heading instance
|
3532
|
+
#
|
3533
|
+
# @param [Hash] a hash of attribute (name, type) pairs where name is
|
3534
|
+
# a Symbol and type is a Class
|
3535
|
+
#
|
3536
|
+
def initialize(attributes)
|
3537
|
+
@attributes = attributes.dup.freeze
|
3538
|
+
end
|
3539
|
+
|
3540
|
+
#
|
3541
|
+
# Returns heading's cardinality
|
3542
|
+
#
|
3543
|
+
def cardinality
|
3544
|
+
attributes.size
|
3545
|
+
end
|
3546
|
+
alias :size :cardinality
|
3547
|
+
alias :count :cardinality
|
3548
|
+
|
3549
|
+
#
|
3550
|
+
# Returns heading's hash code
|
3551
|
+
#
|
3552
|
+
def hash
|
3553
|
+
@hash ||= attributes.hash
|
3554
|
+
end
|
3555
|
+
|
3556
|
+
#
|
3557
|
+
# Checks equality with other heading
|
3558
|
+
#
|
3559
|
+
def ==(other)
|
3560
|
+
other.is_a?(Heading) && (other.attributes == attributes)
|
3561
|
+
end
|
3562
|
+
alias :eql? :==
|
3563
|
+
|
3564
|
+
#
|
3565
|
+
# Converts this heading to a Hash of (name,type) pairs
|
3566
|
+
#
|
3567
|
+
def to_hash
|
3568
|
+
attributes.dup
|
3569
|
+
end
|
3570
|
+
|
3571
|
+
#
|
3572
|
+
# Returns a Heading literal
|
3573
|
+
#
|
3574
|
+
def to_ruby_literal
|
3575
|
+
attributes.empty? ?
|
3576
|
+
"Alf::Heading::EMPTY" :
|
3577
|
+
"Alf::Heading[#{Myrrha.to_ruby_literal(attributes)[1...-1]}]"
|
3578
|
+
end
|
3579
|
+
alias :inspect :to_ruby_literal
|
3580
|
+
|
3581
|
+
EMPTY = Alf::Heading.new({})
|
3582
|
+
end # class Heading
|
3583
|
+
|
3584
|
+
#
|
3585
|
+
# Defines an in-memory relation data structure.
|
3586
|
+
#
|
3587
|
+
# A relation is a set of tuples; a tuple is a set of attribute (name, value)
|
3588
|
+
# pairs. The class implements such a data structure with full relational
|
3589
|
+
# algebra installed as instance methods.
|
3590
|
+
#
|
3591
|
+
# Relation values can be obtained in various ways, for example by invoking
|
3592
|
+
# a relational operator on an existing relation. Relation literals are simply
|
3593
|
+
# constructed as follows:
|
3594
|
+
#
|
3595
|
+
# Alf::Relation[
|
3596
|
+
# # ... a comma list of ruby hashes ...
|
3597
|
+
# ]
|
3598
|
+
#
|
3599
|
+
# See main Alf documentation about relational operators.
|
3600
|
+
#
|
3601
|
+
class Relation
|
3602
|
+
include Iterator
|
3603
|
+
|
3604
|
+
protected
|
3605
|
+
|
3606
|
+
# @return [Set] the set of tuples
|
3607
|
+
attr_reader :tuples
|
3608
|
+
|
3609
|
+
public
|
3610
|
+
|
3611
|
+
#
|
3612
|
+
# Creates a Relation instance.
|
3613
|
+
#
|
3614
|
+
# @param [Set] tuples a set of tuples
|
3615
|
+
#
|
3616
|
+
def initialize(tuples)
|
3617
|
+
raise ArgumentError unless tuples.is_a?(Set)
|
3618
|
+
@tuples = tuples
|
3619
|
+
end
|
3620
|
+
|
3621
|
+
#
|
3622
|
+
# Coerces `val` to a relation.
|
3623
|
+
#
|
3624
|
+
# Recognized arguments are: Relation (identity coercion), Set of ruby hashes,
|
3625
|
+
# Array of ruby hashes, Alf::Iterator.
|
3626
|
+
#
|
3627
|
+
# @return [Relation] a relation instance for the given set of tuples
|
3628
|
+
# @raise [ArgumentError] when `val` is not recognized
|
3629
|
+
#
|
3630
|
+
def self.coerce(val)
|
3631
|
+
case val
|
3632
|
+
when Relation
|
3633
|
+
val
|
3634
|
+
when Set
|
3635
|
+
Relation.new(val)
|
3636
|
+
when Array
|
3637
|
+
Relation.new val.to_set
|
3638
|
+
when Iterator
|
3639
|
+
Relation.new val.to_set
|
3640
|
+
else
|
3641
|
+
raise ArgumentError, "Unable to coerce #{val} to a Relation"
|
3642
|
+
end
|
3643
|
+
end
|
3644
|
+
|
3645
|
+
# (see Relation.coerce)
|
3646
|
+
def self.[](*tuples)
|
3647
|
+
coerce(tuples)
|
3648
|
+
end
|
3649
|
+
|
3650
|
+
#
|
3651
|
+
# (see Iterator#each)
|
3652
|
+
#
|
3653
|
+
def each(&block)
|
3654
|
+
tuples.each(&block)
|
3655
|
+
end
|
3656
|
+
|
3657
|
+
#
|
3658
|
+
# Returns relation's cardinality (number of tuples).
|
3659
|
+
#
|
3660
|
+
# @return [Integer] relation's cardinality
|
3661
|
+
#
|
3662
|
+
def cardinality
|
3663
|
+
tuples.size
|
3664
|
+
end
|
3665
|
+
alias :size :cardinality
|
3666
|
+
alias :count :cardinality
|
3667
|
+
|
3668
|
+
# Returns true if this relation is empty
|
3669
|
+
def empty?
|
3670
|
+
cardinality == 0
|
3671
|
+
end
|
3672
|
+
|
3673
|
+
#
|
3674
|
+
# Install the DSL through iteration over defined operators
|
3675
|
+
#
|
3676
|
+
Operator::each do |op_class|
|
3677
|
+
meth_name = Tools.ruby_case(Tools.class_name(op_class)).to_sym
|
3678
|
+
if op_class.unary?
|
3679
|
+
define_method(meth_name) do |*args|
|
3680
|
+
op = op_class.new(*args).pipe(self)
|
3681
|
+
Relation.coerce(op)
|
3682
|
+
end
|
3683
|
+
elsif op_class.binary?
|
3684
|
+
define_method(meth_name) do |right, *args|
|
3685
|
+
op = op_class.new(*args).pipe([self, Iterator.coerce(right)])
|
3686
|
+
Relation.coerce(op)
|
3687
|
+
end
|
3688
|
+
else
|
3689
|
+
raise "Unexpected operator #{op_class}"
|
3690
|
+
end
|
3691
|
+
end # Operators::each
|
3692
|
+
|
3693
|
+
alias :+ :union
|
3694
|
+
alias :- :minus
|
3695
|
+
|
3696
|
+
# Shortcut for project(attributes, true)
|
3697
|
+
def allbut(attributes)
|
3698
|
+
project(attributes, true)
|
3699
|
+
end
|
3700
|
+
|
3701
|
+
#
|
3702
|
+
# (see Object#hash)
|
3703
|
+
#
|
3704
|
+
def hash
|
3705
|
+
@tuples.hash
|
3706
|
+
end
|
3707
|
+
|
3708
|
+
#
|
3709
|
+
# (see Object#==)
|
3710
|
+
#
|
3711
|
+
def ==(other)
|
3712
|
+
return nil unless other.is_a?(Relation)
|
3713
|
+
other.tuples == self.tuples
|
3714
|
+
end
|
3715
|
+
alias :eql? :==
|
3716
|
+
|
3717
|
+
#
|
3718
|
+
# Returns a textual representation of this relation
|
3719
|
+
#
|
3720
|
+
def to_s
|
3721
|
+
Alf::Renderer.text(self).execute("")
|
3722
|
+
end
|
3723
|
+
|
3724
|
+
#
|
3725
|
+
# Returns an array with all tuples in this relation.
|
3726
|
+
#
|
3727
|
+
# @param [Tools::OrderingKey] an optional ordering key (any argument
|
3728
|
+
# recognized by OrderingKey.coerce is supported here).
|
3729
|
+
# @return [Array] an array of hashes, in requested order (if specified)
|
3730
|
+
#
|
3731
|
+
def to_a(okey = nil)
|
3732
|
+
okey = Tools::OrderingKey.coerce(okey) if okey
|
3733
|
+
ary = tuples.to_a
|
3734
|
+
ary.sort!(&okey.sorter) if okey
|
3735
|
+
ary
|
3736
|
+
end
|
3737
|
+
|
3738
|
+
#
|
3739
|
+
# Returns a literal representation of this relation
|
3740
|
+
#
|
3741
|
+
def to_ruby_literal
|
3742
|
+
"Alf::Relation[" +
|
3743
|
+
tuples.collect{|t| Myrrha.to_ruby_literal(t)}.join(', ') + "]"
|
3744
|
+
end
|
3745
|
+
alias :inspect :to_ruby_literal
|
3746
|
+
|
3747
|
+
DEE = Relation.coerce([{}])
|
3748
|
+
DUM = Relation.coerce([])
|
3749
|
+
end # class Relation
|
3750
|
+
|
3021
3751
|
# Implements a small LISP-like DSL on top of Alf.
|
3022
3752
|
#
|
3023
3753
|
# The lispy dialect is the functional one used in .alf files and in compiled
|
@@ -3061,7 +3791,8 @@ module Alf
|
|
3061
3791
|
if expr.nil?
|
3062
3792
|
instance_eval(&block)
|
3063
3793
|
else
|
3064
|
-
|
3794
|
+
b = _clean_binding
|
3795
|
+
(path ? Kernel.eval(expr, b, path) : Kernel.eval(expr, b))
|
3065
3796
|
end
|
3066
3797
|
end
|
3067
3798
|
|
@@ -3128,8 +3859,52 @@ module Alf
|
|
3128
3859
|
(project child, attributes, true)
|
3129
3860
|
end
|
3130
3861
|
|
3862
|
+
#
|
3863
|
+
# Runs a command as in shell.
|
3864
|
+
#
|
3865
|
+
# Example:
|
3866
|
+
#
|
3867
|
+
# lispy = Alf.lispy(Alf::Environment.examples)
|
3868
|
+
# op = lispy.run(['restrict', 'suppliers', '--', "city == 'Paris'"])
|
3869
|
+
#
|
3870
|
+
def run(argv, requester = nil)
|
3871
|
+
Alf::Command::Main.new(environment).run(argv, requester)
|
3872
|
+
end
|
3873
|
+
|
3131
3874
|
Agg = Alf::Aggregator
|
3875
|
+
DUM = Relation::DUM
|
3876
|
+
DEE = Relation::DEE
|
3877
|
+
|
3878
|
+
private
|
3879
|
+
|
3880
|
+
def _clean_binding
|
3881
|
+
binding
|
3882
|
+
end
|
3883
|
+
|
3132
3884
|
end # module Lispy
|
3133
3885
|
|
3886
|
+
#
|
3887
|
+
# Builds and returns a lispy engine on a specific environment.
|
3888
|
+
#
|
3889
|
+
# Example(s):
|
3890
|
+
#
|
3891
|
+
# # Returns a lispy instance on the default environment
|
3892
|
+
# lispy = Alf.lispy
|
3893
|
+
#
|
3894
|
+
# # Returns a lispy instance on the examples' environment
|
3895
|
+
# lispy = Alf.lispy(Alf::Environment.examples)
|
3896
|
+
#
|
3897
|
+
# # Returns a lispy instance on a folder environment of your choice
|
3898
|
+
# lispy = Alf.lispy(Alf::Environment.folder('path/to/a/folder'))
|
3899
|
+
#
|
3900
|
+
# @see Alf::Environment about available environments and their contract
|
3901
|
+
#
|
3902
|
+
def self.lispy(env = Environment.default)
|
3903
|
+
lispy = Object.new.extend(Lispy)
|
3904
|
+
lispy.environment = Environment.coerce(env)
|
3905
|
+
lispy
|
3906
|
+
end
|
3907
|
+
|
3134
3908
|
end # module Alf
|
3135
|
-
require "alf/
|
3909
|
+
require "alf/text"
|
3910
|
+
require "alf/yaml"
|