$:.unshift('sequel/lib')
$:.unshift('sequel-mongo/lib')
require 'rubygems'
require 'sequel'
require 'benchmark'
SQLITE = Sequel.sqlite('nosequel.sqlite')
MONGO = Sequel.connect('mongo:///nosequel')
SQLITE_LOG = Object.new
def SQLITE_LOG.info(msg, args=[])
puts "SQLite: #{msg}"
end
MONGO_LOG = Object.new
def MONGO_LOG.info(msg, args=[])
puts " Mongo: #{msg}"
end
OBJ_MAP = {SQLITE=>'SQlite: ', MONGO=>' Mongo: '}
SQLITE.create_table!(:t) do
primary_key :_id
Integer :a
Integer :b
String :c
end
class ST < Sequel::Model(SQLITE[:t])
many_to_one :square_root, :class=>self, :key=>:a, :primary_key=>:b
end
MONGO[:t].insert(:a=>1, :b=>2, :c=>3)
class MT < Sequel::Model(MONGO[:t])
many_to_one :square_root, :class=>self, :key=>:a, :primary_key=>:b
end
MONGO[:t].delete
M = {MONGO=>MT, SQLITE=>ST}
def both_log(code)
both(code, :log=>true)
end
def both_quiet(code)
both(code, :quiet=>true)
end
def both(code, opts={})
puts "Code: #{code}" unless opts[:quiet]
rs = [[SQLITE, SQLITE_LOG], [MONGO, MONGO_LOG]].map do |db, log|
db.loggers << log if opts[:log]
begin
eval(code)
rescue => e
"#{db.class.name} raised exception: #{e}"
ensure
db.loggers.clear
end
end
unless opts[:quiet]
r1 = rs.first
if rs.all?{|r| r == r1}
puts "Same answer for all databases: #{r1.inspect}"
else
puts "Different answers:"
puts [SQLITE, MONGO].zip(rs).map{|obj, r| "#{OBJ_MAP[obj]}#{r.inspect}"}
end
puts ""
end
end
def puts_eval(s)
puts s
eval s
end
# Total slides
$ts = 0
SLIDES = {}
def slide(n, &block)
SLIDES[$ts] = n
Object.send(:define_method, "s#{$ts}", &block)
$ts += 1
end
def last_slide
send("s#{$ts-1}")
end
# Current slide
$cs = 0
def n
s = SLIDES[$cs]
puts s
puts('-' * s.length)
send("s#{$cs}")
$cs += 1
end
slide("NoSequel") do
puts "Sequel = The Database Toolkit for Ruby"
puts "NoSQL = (Not at all|Not only) SQL Databases"
puts "NoSequel = Using NoSQL databases with Sequel"
puts "Me = Jeremy Evans, Sequel maintainer"
end
slide("MongoDB") do
puts "MongoDB = NoSQL Document Store"
puts "sequel-mongo = MongoDB driver for Sequel"
end
slide("How?") do
puts "1) Sequel uses a DSL instead of literal SQL strings."
puts "2) DSL produces objects that represent concepts."
puts "3) Treat those objects specially in the sequel-mongo driver."
puts "4) Compile filter objects to javascript instead of SQL."
end
slide("Selecting Records") do
both_log "db[:t].map(:a)"
end
slide("Inserting Records") do
both_log "db[:t].insert(:a=>1, :b=>2)"
both "db[:t].map(:a)"
end
slide("Updating Records") do
both_log "db[:t].update(:a=>10, :b=>20)"
both "db[:t].map(:a)"
end
slide("Deleting Records") do
both_log "db[:t].delete"
both "db[:t].map(:a)"
end
slide("Add Some More Data") do
puts '10.times{|i| db[:t].insert(:a=>i, :b=>i * i)}'
10.times{|i| both_quiet "db[:t].insert(:a=>#{i}, :b=>#{i * i})"}
end
slide("Ordering") do
both_log "db[:t].order(:b.desc).map{|x| x.values_at(:a, :b)}"
end
slide("Equals") do
both_log "db[:t].filter(:a=>5).map(:b)"
end
slide("Not Equals") do
both_log "db[:t].exclude(:a=>5).order(:b).map(:b)"
end
slide("Inequality") do
both_log "db[:t].filter{a > 5}.order(:b).map(:b)"
end
slide("IS NULL/undefined") do
both "db[:t].insert(:b=>100)"
both_log "db[:t].filter(:a=>nil).order(:b).map(:b)"
both_log "db[:t].exclude(:a=>nil).order(:b).map(:b)"
both "db[:t].filter(:b=>100).delete"
end
slide("Limits") do
both_log "db[:t].filter{a < 5}.order(:b.desc).limit(2).map(:b)"
end
slide("Offsets") do
both_log "db[:t].filter{a < 5}.order(:b.desc).limit(2, 1).map(:b)"
end
slide("Selecting only certain columns") do
both_log "db[:t].filter{a < 5}.order(:b.desc).select(:a).all"
end
slide("Counting") do
both_log "db[:t].filter{a < 5}.count"
end
slide("Standard math operators") do
both_log "db[:t].filter((:a + 1) * 5 - :b > 0).order(:b).map(:b)"
end
slide("Bitwise math operators") do
both_log "db[:t].filter((:a.sql_number << 2) / (:b.sql_number | 3) >= 1).order(:b).map(:b)"
end
slide("Add some strings to the table") do
puts_eval "NAMES =%w'Joe Jim Bob John Pat Kat Don Paul Alex Ali'"
puts "NAMES.each_with_index{|n,i| db[:t].filter(:a=>i).update(:c=>n)"
NAMES.each_with_index{|n,i| both_quiet "db[:t].filter(:a=>#{i}).update(:c=>#{n.inspect})"}
both "db[:t].order(:a).map{|x| x.values_at(:a, :c)}"
end
slide("Search with Regexps") do
both_log "db[:t].filter(:c=>/^J/).order(:c).map(:c)"
end
slide("Search with LIKE") do
both_log "db[:t].filter(:c.like('J%')).order(:c).map(:c)"
end
slide("String concatenation") do
both_log "db[:t].filter([:c, :c].sql_string_join(' ').like('J% Jo%')).order(:c).map(:c)"
end
slide("IN/NOT IN with array") do
both_log "db[:t].filter(:c=>%w'Ali Alex').order(:c).map(:c)"
both_log "db[:t].exclude(:c=>%w'Ali Alex').order(:c).map(:c)"
end
slide("Complex Expression Example 1") do
both_log "db[:t].filter{a < 3}.or{a > 6}.order(:a).map(:a)"
end
slide("Complex Expression Example 2") do
both_log "db[:t].filter{((a > 3) & c.like('A%')) | {b=>[4, 9, 16]}}.order(:a).map(:a)"
end
slide("Complex Expression Example 3") do
both_log "db[:t].filter{((a * 3 + (b.sql_number >> 1) > b - 10) & ~c.ilike('%a%')) | ({b=>[4, 9, 16]} & {a=>3, :b=>16}.sql_or)}.order(:a).map(:a)"
end
slide("Case Statements") do
both_log "db[:t].filter({1=>9, 5=>0}.case(1, :a) * :b * 10 > 90).order(:a).map(:a)"
end
slide("Casting") do
both_log "db[:t].filter(:a.cast_string + :a.cast_string => '11').order(:a).map(:a)"
both_log "db[:t].filter(\"'1'\".lit.cast_numeric + :a => 3).order(:a).map(:a)"
end
slide("Models and attribute access") do
both_log "a = M[db].order(:a.desc).first; a.a"
end
slide("Model associations") do
both_log "M[db].order(:a.desc).first.square_root.a"
end
slide("Doesn't Work: Updating multi objects with a filter") do
both_log "db[:t].filter{a < 5}.update(:b=>30)"
both "db[:t].order(:a).map(:b)"
end
slide("The necessary completely flawed benchmark") do
both_quiet "db[:t].delete"
puts "SQLite"
n = 200
Benchmark.bm(25) do |x|
x.report("SQLite #{n/10} inserts:"){(n/10).times{|i| SQLITE[:t].insert(:a=>i, :b=>n - i)}}
x.report("Mongo #{n} inserts:"){n.times{|i| MONGO[:t].insert(:a=>i, :b=>n - i)}}
x.report("SQLite #{(n/10)} lookups by id:"){(n/10).times{|i| SQLITE[:t][:a=>i]}}
x.report("Mongo #{n} lookups by id:"){n.times{|i| MONGO[:t][:a=>i]}}
x.report("SQLite #{n/10} select alls:"){(n/10).times{|i| SQLITE[:t].all}}
x.report("Mongo #{n/10} select alls:"){(n/10).times{|i| MONGO[:t].all}}
x.report("SQLite #{(n/10)} updates:"){(n/10).times{|i| SQLITE[:t].filter(:a=>i).update(:b=>i)}}
x.report("Mongo #{n} updates:"){n.times{|i| MONGO[:t].filter(:a=>i).update(:b=>i)}}
x.report("SQLite #{(n/10)} deletes:"){(n/10).times{|i| SQLITE[:t].filter(:a=>i).delete}}
x.report("Mongo #{n} deletes:"){n.times{|i| MONGO[:t].filter(:a=>i).delete}}
end
end
slide("The End") do
puts "sequel-mongo: http://github.com/jeremyevans/sequel-mongo"
end