README.md in bmg-0.16.4 vs README.md in bmg-0.16.5

- old
+ new

@@ -1,3 +1,82 @@ -## Bmg, Alf's successor, the relational algebra! +# Bmg, a relational algebra (Alf's successor)! -Coming soon. \ No newline at end of file +Bmg is a relational algebra implemented as a ruby library. It implements the +[Relation as First-Class Citizen](http://www.try-alf.org/blog/2013-10-21-relations-as-first-class-citizen) +paradigm contributed with Alf a few years ago. + +Like Alf, Bmg can be used to query relations in memory, from various files, +SQL databases, and any data sources that can be seen as serving relations. +Cross data-sources joins are supported, as with Alf. + +Unlike Alf, Bmg does not make any core ruby extension and exposes the +object-oriented syntax only (not Alf's functional one). Bmg implementation is +also much simpler, and make its easier to implement user-defined relations. + +## Example + +``` +require 'bmg' +require 'json' + +suppliers = Bmg::Relation.new([ + { sid: "S1", name: "Smith", status: 20, city: "London" }, + { sid: "S2", name: "Jones", status: 10, city: "Paris" }, + { sid: "S3", name: "Blake", status: 30, city: "Paris" }, + { sid: "S4", name: "Clark", status: 20, city: "London" }, + { sid: "S5", name: "Adams", status: 30, city: "Athens" } +]) + +by_city = suppliers + .restrict(Predicate.neq(status: 30)) + .extend(upname: ->(t){ t[:name].upcase }) + .group([:sid, :name, :status], :suppliers_in) + +puts JSON.pretty_generate(by_city) +``` + +## Connecting to a SQL database + +Bmg requires `sequel >= 3.0` to connect to SQL databases. + +``` +require 'sqlite3' +require 'bmg' +require 'bmg/sequel' + +DB = Sequel.connect("sqlite://suppliers-and-parts.db") + +suppliers = Bmg.sequel(:suppliers, DB) + +puts suppliers + .restrict(Predicate.neq(status: 30)) + .to_sql + +# SELECT `t1`.`sid`, `t1`.`name`, `t1`.`status`, `t1`.`city` FROM `suppliers` AS 't1' WHERE (`t1`.`status` != 30) +``` + +## Supported operators + +``` +r.allbut([:a, :b, ...]) # remove specified attributes +r.autowrap(split: '_') # structure a flat relation, split: '_' is the default +r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported +r.constants(x: 12, ...) # add constant attributes (sometimes useful in unions) +r.extend(x: ->(t){ ... }, ...) # add computed attributes +r.group([:a, :b, ...], :x) # relation-valued attribute from attributes +r.image(right, :x, [:a, :b, ...]) # relation-valued attribute from another relation +r.join(right, [:a, :b, ...]) # natural join on a join key +r.join(right, :a => :x, :b => :y, ...) # natural join after right reversed renaming +r.matching(right, [:a, :b, ...]) # semi join, aka where exists +r.matching(right, :a => :x, :b => :y, ...) # semi join, after right reversed renaming +r.not_matching(right, [:a, :b, ...]) # inverse semi join, aka where not exists +r.not_matching(right, :a => :x, ...) # inverse semi join, after right reversed renaming +r.page([[:a, :asc], ...], 12, page_size: 10) # paging, using an explicit ordering +r.prefix(:foo_, but: [:a, ...]) # prefix kind of renaming +r.project([:a, :b, ...]) # keep specified attributes only +r.rename(a: :x, b: :y, ...) # rename some attributes +r.restrict(a: "foo", b: "bar", ...) # relational restriction, aka where +r.rxmatch([:a, :b, ...], /xxx/) # regex match kind of restriction +r.summarize([:a, :b, ...], x: :sum) # relational summarization +r.suffix(:_foo, but: [:a, ...]) # suffix kind of renaming +r.union(right) # relational union +```