require 'rubygems' require 'thread' require_gem 'postgres' require 'metaid' module ServerSide class Database attr_reader :conn def initialize(opts = {}) @opts = opts @mutex = Mutex.new @conn = make_connection end def from(*args); query.from(*args); end def select(*args); query.select(*args); end end class Dataset include Enumerable def initialize(db, opts = {}) @db = db @opts = opts || {} end def dup_merge(opts) self.class.new(@db, @opts.merge(opts)) end # sql helpers def field_list(fields) case fields when Array: if fields.empty? "*" else fields.map {|i| field_name(i)}.join(", ") end else fields end end def field_name(field) if field === Symbol s = field.to_s if s =~ /(.*)___(.*)/ s = "%s AS %s" % [$1, $2] end s.split('__').join('.') else field.to_s end end def source_list(source) case source when Array: source.join(', ') when Hash: source.map {|kv| "#{kv[1]} AS #{kv[0]}"} else source end end # DSL constructors def from(source) dup_merge(:source => source) end def select(*fields) fields = fields.first if fields.size == 1 dup_merge(:fields => fields) end def from!(source) @sql = nil @opts[:source] = source self end def select!(*fields) @sql = nil fields = fields.first if fields.size == 1 @opts[:fields] = fields self end end end module Postgres PG_TYPES = { 16 => :to_bool, 20 => :to_i, 21 => :to_i, 22 => :to_i, 23 => :to_i, 700 => :to_f, 701 => :to_f } class Database < ServerSide::Database def make_connection PGconn.connect( @opts[:host] || '127.0.0.1', @opts[:port] || 5432, '', '', @opts[:database] || 'test', @opts[:user] || 'postgres', @opts[:password] || '240374' ) end def query(opts = nil) Postgres::Dataset.new(self, opts) end end class Dataset < ServerSide::Dataset SELECT = "SELECT %s FROM %s".freeze LIMIT = "LIMIT %s".freeze def each(field = nil, &block) execute @result.each do |r| row = fetch_row(r) block.call(field ? row[field] : row) end self end def all(field = nil) execute result = [] @result.each do |r| row = fetch_row(r) result << (field ? row[field] : row) end result end def first execute(@opts.merge(:limit => 1)) @result.each do |r| break fetch_row(r) end end def execute(opts = nil) @result = @db.conn.exec(compile_sql(opts)) @fields = @result.fields.map {|s| s.to_sym} @types = (0..(@result.num_fields - 1)).map {|idx| @result.type(idx)} compile_row_fetcher end def compile_sql(opts = nil) custom_opts = !opts.nil? return @sql if @sql && custom_opts opts = @opts if opts.nil? fields = opts[:fields] select_fields = fields ? field_list(fields) : "*" select_source = source_list(opts[:source]) select_clause = SELECT % [select_fields, select_source] limit = opts[:limit] limit_clause = limit ? LIMIT % [limit] : '' sql = [select_clause, limit_clause].join(' ') @sql = sql unless custom_opts end def compile_row_fetcher parts = (0..(@result.num_fields - 1)).inject([]) do |m, f| translate_fn = PG_TYPES[@types[f]] translator = translate_fn ? ".#{translate_fn}" : "" m << ":#{@fields[f]} => r[#{f}]#{translator}" end l = eval("lambda {|r|{#{parts.join(',')}}}") extend(Module.new {define_method(:fetch_row, &l)}) end end end DB = Postgres::Database.new $d = DB.query.from(:foo) $d.each {|n| puts n[:name]} __END__ class Database attr_reader :conn def initialize @conn = PGconn.connect('localhost', 5432, '', '', 'reality_development', 'postgres', '240374') end def query(sql) Query.new(@conn, sql) end def select(opts) return Query.new(@conn, opts[:sql]) if opts[:sql] if opts[:select] fields = opts[:select].is_a?(Array) ? opts[:select].join(',') : opts[:select] else fields = '*' end sql = "SELECT #{fields} FROM #{opts[:table]}" sql << " WHERE #{opts[:where]}" if opts[:where] Query.new(@conn, sql) end def select_count(opts) select(opts.merge(:select => 'COUNT(*)')).each {|r| return r[:count]} end def literal_repr(o) if String === o "'#{o}'" else o.to_s end end def [](name) Filter.new(self, {:table => name}) end end PG_TYPES = { 16 => :to_bool, 20 => :to_i, 21 => :to_i, 22 => :to_i, 23 => :to_i, 700 => :to_f, 701 => :to_f } class Query attr_reader :result, :fields def initialize(conn, sql) @conn = conn @sql = sql end def execute(*args) @result = @conn.exec(@sql) @fields = @result.fields.map {|s| s.to_sym} @types = (0..(@result.num_fields - 1)).map {|idx| @result.type(idx)} compile_row_fetcher end def compile_row_fetcher parts = (0..(@result.num_fields - 1)).inject([]) do |m, f| translate_fn = PG_TYPES[@types[f]] translator = translate_fn ? ".#{translate_fn}" : "" m << ":#{@fields[f]} => r[#{f}]#{translator}" end l = eval("lambda {|r|{#{parts.join(',')}}}") extend(Module.new {define_method(:fetch_row, &l)}) end def each(&block) execute @result.each do |r| block.call(fetch_row(r)) end self end def row_count @result.num_tuples end end class Filter def initialize(db, opts) @db = db @opts = opts end def compile_conditions(conditions) return nil unless conditions result = conditions.shift while result && (result =~ /\?/) a = @db.literal_repr(conditions.shift) result.sub!(/\?/, a) end result end def filter(conditions) Filter.new(@db, @opts.merge(:where => compile_conditions(conditions))) end alias_method :select, :filter def first(opts = nil) opts ? (opts[:limit] = 1) : (opts = {:limit => 1}) each {|r| return r} end def[](attributes) params = [] rule = attributes.inject('') {|m, kv| params << kv[1]; m << "(#{kv[0]} = ?)"} filter(params.unshift(rule)).first end def all(opts = nil) a = [] each(opts) {|r| a << r} a end def each(opts = nil, &block) @db.select(opts ? @opts.merge(opts) : @opts).each(&block) end def count(opts = nil) @db.select_count(opts ? @opts.merge(opts) : @opts) end def summary(*fields) @db.select(@opts.merge(:select => fields)).each {|r| return r} end def update(values) @db.update(@opts, values) end end class Symbol def max "max(#{to_s}) AS max_#{to_s}" end def min "min(#{to_s}) AS min_#{to_s}" end end ################################################################################ ################################################################################ $db = Database.new $t = $db[:states] #puts $db[:nodes].all.inspect puts $t.summary(:quality.max, :quality.min, :stamp.max, :stamp.min).inspect ################################################################################ # Table creation ################################################################################ #$db.create_table(:states, # :fields => { # id => :int, # :node_id => :int, # :quality => :int, # :stamp => :double, # :value => :text, # :datatype => :int}, # :index => :id #); ################################################################################ # Joins ################################################################################ # # # # # # # # # # require 'benchmark' def t1 $t.each {|n| n[:node_id]} end def t2 $t.filter(nil).each() {|n| n.inspect} end n = 100 Benchmark.bm do |x| x.report {n.times {t1}} x.report {n.times {t2}} end