swift 0.4.1

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.
@@ -0,0 +1,48 @@
1
+ # Extension.
2
+ require_relative '../ext/swift'
3
+ require_relative 'swift/adapter'
4
+ require_relative 'swift/attribute'
5
+ require_relative 'swift/db'
6
+ require_relative 'swift/header'
7
+ require_relative 'swift/scheme'
8
+ require_relative 'swift/type'
9
+
10
+ module Swift
11
+ class << self
12
+ def setup name, type, options = {}
13
+ unless type.kind_of?(Class) && type < Swift::Adapter
14
+ raise TypeError, "Expected +type+ Swift::Adapter subclass but got #{type.inspect}"
15
+ end
16
+ (@repositories ||= {})[name] = type.new(options)
17
+ end
18
+
19
+ def db name = nil, &block
20
+ # I pilfered the logic from DM but I don't really understand what is/isn't thread safe.
21
+ scopes = (Thread.current[:swift_db] ||= [])
22
+ repository = if name || scopes.empty?
23
+ @repositories[name || :default] or raise "Unknown db '#{name || :default}', did you forget to #setup?"
24
+ else
25
+ scopes.last
26
+ end
27
+
28
+ if block_given?
29
+ begin
30
+ scopes.push(repository)
31
+ block.call(repository)
32
+ ensure
33
+ scopes.pop
34
+ end
35
+ end
36
+ repository
37
+ end
38
+
39
+ def schema
40
+ @schema ||= []
41
+ end
42
+
43
+ def migrate! name = nil
44
+ db(name){ schema.each(&:migrate!)}
45
+ end
46
+ end
47
+ end # Swift
48
+
@@ -0,0 +1,120 @@
1
+ module Swift
2
+ #--
3
+ # TODO: Still not convinced all and first are necessary.
4
+ class Adapter
5
+ attr_reader :options
6
+
7
+ def identity_map
8
+ @identity_map ||= IdentityMap.new
9
+ end
10
+
11
+ def get scheme, keys
12
+ relation = scheme.new(keys)
13
+ prepare_get(scheme).execute(*relation.tuple.values_at(*scheme.header.keys)).first
14
+ end
15
+
16
+ def all scheme, conditions = '', *binds, &block
17
+ where = "where #{exchange_names(scheme, conditions)}" unless conditions.empty?
18
+ prepare(scheme, "select * from #{scheme.store} #{where}").execute(*binds, &block)
19
+ end
20
+
21
+ def first scheme, conditions = '', *binds, &block
22
+ all(scheme, "#{conditions} limit 1", *binds, &block).first
23
+ end
24
+
25
+ def create scheme, *relations
26
+ statement = prepare_create(scheme)
27
+ relations.map do |relation|
28
+ relation = scheme.new(relation) unless relation.kind_of?(scheme)
29
+ if statement.execute(*relation.tuple.values_at(*scheme.header.insertable)) && scheme.header.serial
30
+ relation.tuple[scheme.header.serial] = statement.insert_id
31
+ end
32
+ relation
33
+ end
34
+ end
35
+
36
+ def update scheme, *relations
37
+ statement = prepare_update(scheme)
38
+ relations.map do |relation|
39
+ relation = scheme.new(relation) unless relation.kind_of?(scheme)
40
+ statement.execute(*relation.tuple.values_at(*scheme.header.updatable, *scheme.header.keys))
41
+ end
42
+ end
43
+
44
+ def destroy scheme, *relations
45
+ statement = prepare_destroy(scheme)
46
+ relations.map do |relation|
47
+ relation = scheme.new(relation) unless relation.kind_of?(scheme)
48
+ if result = statement.execute(*relation.tuple.values_at(*scheme.header.keys))
49
+ relation.freeze
50
+ end
51
+ result
52
+ end
53
+ end
54
+
55
+ def migrate! scheme
56
+ keys = scheme.header.keys
57
+ fields = scheme.header.map{|p| field_definition(p)}.join(', ')
58
+ fields += ", primary key (#{keys.join(', ')})" unless keys.empty?
59
+
60
+ execute("drop table if exists #{scheme.store}")
61
+ execute("create table #{scheme.store} (#{fields})")
62
+ end
63
+
64
+ protected
65
+ def exchange_names scheme, query
66
+ query.gsub(/:(\w+)/){ scheme.send($1.to_sym).field }
67
+ end
68
+
69
+ def returning?
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def prepare_cached scheme, name, &block
74
+ @prepared ||= Hash.new{|h,k| h[k] = Hash.new} # Autovivification please Matz!
75
+ @prepared[scheme][name] ||= prepare(scheme, yield)
76
+ end
77
+
78
+ def prepare_get scheme
79
+ prepare_cached(scheme, :get) do
80
+ where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
81
+ "select * from #{scheme.store} where #{where} limit 1"
82
+ end
83
+ end
84
+
85
+ def prepare_create scheme
86
+ prepare_cached(scheme, :create) do
87
+ values = (['?'] * scheme.header.insertable.size).join(', ')
88
+ returning = "returning #{scheme.header.serial}" if scheme.header.serial and returning?
89
+ "insert into #{scheme.store} (#{scheme.header.insertable.join(', ')}) values (#{values}) #{returning}"
90
+ end
91
+ end
92
+
93
+ def prepare_update scheme
94
+ prepare_cached(scheme, :update) do
95
+ set = scheme.header.updatable.map{|field| "#{field} = ?"}.join(', ')
96
+ where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
97
+ "update #{scheme.store} set #{set} where #{where}"
98
+ end
99
+ end
100
+
101
+ def prepare_destroy scheme
102
+ prepare_cached(scheme, :destroy) do
103
+ where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
104
+ "delete from #{scheme.store} where #{where}"
105
+ end
106
+ end
107
+
108
+ def field_definition attribute
109
+ "#{attribute.field} " + case attribute
110
+ when Type::String then 'text'
111
+ when Type::Integer then attribute.serial ? 'serial' : 'integer'
112
+ when Type::Float then 'float'
113
+ when Type::BigDecimal then 'numeric'
114
+ when Type::Time then 'timestamp'
115
+ when Type::Boolean then 'boolean'
116
+ else 'text'
117
+ end
118
+ end
119
+ end # Adapter
120
+ end # Swift
@@ -0,0 +1,25 @@
1
+ module Swift
2
+ class Attribute
3
+ attr_reader :name, :field, :key, :serial
4
+
5
+ def initialize scheme, name, options = {}
6
+ @name = name
7
+ @default = options.fetch(:default, nil)
8
+ @field = options.fetch(:field, name)
9
+ @key = options.fetch(:key, false)
10
+ @serial = options.fetch(:serial, false)
11
+ define_scheme_methods(scheme)
12
+ end
13
+
14
+ def default
15
+ @default.respond_to?(:call) ? @default.call : (@default.nil? ? nil : @default.dup)
16
+ end
17
+
18
+ def define_scheme_methods scheme
19
+ scheme.class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ def #{name}; tuple.fetch(:#{field}) end
21
+ def #{name}= value; tuple.store(:#{field}, value) end
22
+ RUBY
23
+ end
24
+ end # Attribute
25
+ end # Swift
@@ -0,0 +1,39 @@
1
+ module Swift
2
+ module DB
3
+ class Mysql < Adapter
4
+ def initialize options = {}
5
+ super options.update(driver: 'mysql')
6
+ execute('select unix_timestamp() - unix_timestamp(utc_timestamp()) as offset') {|r| @tzoffset = r[:offset] }
7
+ end
8
+
9
+ def timezone *args
10
+ super(*args)
11
+ execute('select unix_timestamp() - unix_timestamp(utc_timestamp()) as offset') {|r| @tzoffset = r[:offset] }
12
+ @tzoffset
13
+ end
14
+
15
+ def returning?
16
+ false
17
+ end
18
+ end # Mysql
19
+
20
+ class Postgres < Adapter
21
+ def initialize options = {}
22
+ super options.update(driver: 'postgresql')
23
+ sql = "select extract(epoch from now())::bigint - extract(epoch from now() at time zone 'UTC')::bigint"
24
+ execute('%s as offset' % sql) {|r| @tzoffset = r[:offset] }
25
+ end
26
+
27
+ def timezone *args
28
+ super(*args)
29
+ sql = "select extract(epoch from now())::bigint - extract(epoch from now() at time zone 'UTC')::bigint"
30
+ execute('%s as offset' % sql) {|r| @tzoffset = r[:offset] }
31
+ @tzoffset
32
+ end
33
+
34
+ def returning?
35
+ true
36
+ end
37
+ end # Postgres
38
+ end # DB
39
+ end # Swift
@@ -0,0 +1,45 @@
1
+ module Swift
2
+ class Header
3
+ include Enumerable
4
+
5
+ def initialize *attributes
6
+ @attributes = {}
7
+ push *attributes unless attributes.empty?
8
+ end
9
+
10
+ def new_tuple
11
+ Hash[insertable.map{|field| [field, @attributes[field].default]}]
12
+ end
13
+
14
+ def push *attributes
15
+ @attributes.update Hash[attributes.map{|attribute| [attribute.field, attribute]}]
16
+ end
17
+
18
+ def insertable
19
+ @insertable ||= all - [serial]
20
+ end
21
+
22
+ def updatable
23
+ @updatable ||= all - (keys | [serial])
24
+ end
25
+
26
+ def all
27
+ @all ||= @attributes.keys
28
+ end
29
+
30
+ def serial
31
+ return @serial if defined? @serial
32
+ serial = find(&:serial)
33
+ @serial = serial ? serial.field : nil
34
+ end
35
+
36
+ def keys
37
+ @keys ||= select(&:key).map(&:field)
38
+ end
39
+
40
+ def each &block
41
+ @attributes.values.each{|v| yield v}
42
+ end
43
+ end # Header
44
+ end # Swift
45
+
@@ -0,0 +1,41 @@
1
+ module Swift
2
+ # Weak hash set.
3
+ #--
4
+ # TODO: Is 'hash set' the real name for a hash where both the keys and values must be unique?
5
+ class IdentityMap
6
+ def initialize
7
+ @cache, @reverse_cache, @finalize = {}, {}, method(:finalize)
8
+ end
9
+
10
+ def get key
11
+ value_id = @cache[key]
12
+ return ObjectSpace._id2ref(value_id) unless value_id.nil?
13
+ nil
14
+ end
15
+
16
+ #--
17
+ # TODO: Barf if the value.object_id already exists in the cache.
18
+ def set key, value
19
+ @reverse_cache[value.object_id] = key
20
+ @cache[key] = value.object_id
21
+ ObjectSpace.define_finalizer(value, @finalize)
22
+ end
23
+
24
+ private
25
+ def finalize value_id
26
+ @cache.delete @reverse_cache.delete value_id
27
+ end
28
+ end # IdentityMap
29
+
30
+ class Scheme
31
+ def self.load tuple
32
+ im = [self, *tuple.values_at(*header.keys)]
33
+ unless scheme = Swift.db.identity_map.get(im)
34
+ scheme = allocate
35
+ scheme.tuple = tuple
36
+ Swift.db.identity_map.set(im, scheme)
37
+ end
38
+ scheme
39
+ end
40
+ end # Scheme
41
+ end # Swift
@@ -0,0 +1,74 @@
1
+ require 'eventmachine'
2
+
3
+ module Swift
4
+ class Pool
5
+ module Handler
6
+ def initialize request, pool
7
+ @request, @pool = request, pool
8
+ end
9
+
10
+ def socket
11
+ @request.socket
12
+ end
13
+
14
+ def notify_readable
15
+ if @request.process
16
+ detach
17
+ @pool.detach self
18
+ end
19
+ end
20
+ end # Handler
21
+
22
+ def initialize size, options
23
+ @pool = Swift::ConnectionPool.new size, options
24
+ @stop_reactor = EM.reactor_running? ? false : true
25
+ @pending = {}
26
+ @queue = []
27
+ end
28
+
29
+ def attach c
30
+ @pending[c] = true
31
+ end
32
+
33
+ def detach c
34
+ @pending.delete(c)
35
+ if @queue.empty?
36
+ EM.stop if @stop_reactor && @pending.empty?
37
+ else
38
+ sql, bind, callback = @queue.shift
39
+ execute(sql, *bind, &callback)
40
+ end
41
+ end
42
+
43
+ def attached? fd
44
+ @pending.keys.select{|c| c.socket == fd}.length > 0
45
+ end
46
+
47
+ def execute sql, *bind, &callback
48
+ request = @pool.execute sql, *bind, &callback
49
+ # TODO EM throws exceptions in C++ land which are not trapped in the extension.
50
+ # This is somehow causing everything to unravel and result in a segfault which
51
+ # I cannot track down. I'll buy a beer for someone who can get this fixed :)
52
+ # Oh, here it throws an exception if we try to attach same fd twice.
53
+ if request && !attached?(request.socket)
54
+ EM.watch(request.socket, Handler, request, self) do |c|
55
+ attach c
56
+ c.notify_writable = false
57
+ c.notify_readable = true
58
+ end
59
+ else
60
+ @queue << [ sql, bind, callback ]
61
+ end
62
+ end
63
+
64
+ def run &block
65
+ EM.run{ yield self }
66
+ end
67
+ end # Pool
68
+
69
+ def self.pool size, name = :default, &block
70
+ pool = Pool.new(size, Swift.db(name).options)
71
+ pool.run(&block) if block_given?
72
+ pool
73
+ end
74
+ end # Swift
@@ -0,0 +1,70 @@
1
+ module Swift
2
+ class Scheme
3
+ attr_accessor :tuple
4
+ alias_method :scheme, :class
5
+
6
+ def initialize options = {}
7
+ @tuple = scheme.header.new_tuple
8
+ options.each{|k, v| send(:"#{k}=", v)}
9
+ end
10
+
11
+ def update options = {}
12
+ options.each{|k, v| send(:"#{k}=", v)}
13
+ Swift.db.update(scheme, self)
14
+ end
15
+
16
+ def destroy
17
+ Swift.db.destroy(scheme, self)
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :header
22
+
23
+ def inherited klass
24
+ klass.header = Header.new
25
+ klass.header.push(*header) if header
26
+ Swift.schema.push(klass) if klass.name
27
+ end
28
+
29
+ def load tuple
30
+ scheme = allocate
31
+ scheme.tuple = tuple
32
+ scheme
33
+ end
34
+
35
+ def attribute name, type, options = {}
36
+ header.push(attribute = type.new(self, name, options))
37
+ (class << self; self end).send(:define_method, name, lambda{ attribute })
38
+ end
39
+
40
+ def store name = nil
41
+ name ? @store = name : @store
42
+ end
43
+
44
+ def migration &migration
45
+ (class << self; self end).send(:define_method, :migrate!, lambda{ Swift.db.instance_eval(&migration) })
46
+ end
47
+
48
+ def migrate!
49
+ Swift.db.migrate!(self)
50
+ end
51
+
52
+ def create options = {}
53
+ Swift.db.create(self, options)
54
+ end
55
+
56
+ def get keys
57
+ Swift.db.get(self, keys)
58
+ end
59
+
60
+ def all conditions = '', *binds, &block
61
+ Swift.db.all(self, conditions, *binds, &block)
62
+ end
63
+
64
+ def first conditions = '', *binds, &block
65
+ Swift.db.first(self, conditions, *binds, &block)
66
+ end
67
+ end
68
+ end # Scheme
69
+ end # Swift
70
+