oinky 0.1.0

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.
Files changed (41) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +141 -0
  3. data/ext/extconf.rb +79 -0
  4. data/ext/include/oinky.h +424 -0
  5. data/ext/include/oinky.hpp +63 -0
  6. data/ext/include/oinky/nky_base.hpp +1116 -0
  7. data/ext/include/oinky/nky_core.hpp +1603 -0
  8. data/ext/include/oinky/nky_cursor.hpp +665 -0
  9. data/ext/include/oinky/nky_dialect.hpp +107 -0
  10. data/ext/include/oinky/nky_error.hpp +164 -0
  11. data/ext/include/oinky/nky_fixed_table.hpp +710 -0
  12. data/ext/include/oinky/nky_handle.hpp +334 -0
  13. data/ext/include/oinky/nky_index.hpp +1038 -0
  14. data/ext/include/oinky/nky_log.hpp +15 -0
  15. data/ext/include/oinky/nky_merge_itr.hpp +403 -0
  16. data/ext/include/oinky/nky_model.hpp +110 -0
  17. data/ext/include/oinky/nky_pool.hpp +760 -0
  18. data/ext/include/oinky/nky_public.hpp +808 -0
  19. data/ext/include/oinky/nky_serializer.hpp +1625 -0
  20. data/ext/include/oinky/nky_strtable.hpp +504 -0
  21. data/ext/include/oinky/nky_table.hpp +1996 -0
  22. data/ext/nky_lib.cpp +390 -0
  23. data/ext/nky_lib_core.hpp +212 -0
  24. data/ext/nky_lib_index.cpp +158 -0
  25. data/ext/nky_lib_table.cpp +224 -0
  26. data/lib/oinky.rb +1284 -0
  27. data/lib/oinky/compiler.rb +106 -0
  28. data/lib/oinky/cpp_emitter.rb +311 -0
  29. data/lib/oinky/dsl.rb +167 -0
  30. data/lib/oinky/error.rb +19 -0
  31. data/lib/oinky/modelbase.rb +12 -0
  32. data/lib/oinky/nbuffer.rb +152 -0
  33. data/lib/oinky/normalize.rb +132 -0
  34. data/lib/oinky/oc_builder.rb +44 -0
  35. data/lib/oinky/query.rb +193 -0
  36. data/lib/oinky/rb_emitter.rb +147 -0
  37. data/lib/oinky/shard.rb +40 -0
  38. data/lib/oinky/testsup.rb +104 -0
  39. data/lib/oinky/version.rb +9 -0
  40. data/oinky.gemspec +36 -0
  41. metadata +120 -0
@@ -0,0 +1,19 @@
1
+ # This source is distributed under the terms of the MIT License. Refer
2
+ # to the 'LICENSE' file for details.
3
+ #
4
+ # Copyright (c) Jacob Lacouture, 2012
5
+
6
+ module Oinky
7
+ class OinkyException < StandardError
8
+ attr_reader :code
9
+ def initialize(*a)
10
+ if a.size > 1
11
+ super(*a[1..-1])
12
+ @code = a[0]
13
+ else
14
+ super(*a)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,12 @@
1
+ module Oinky
2
+ module Model
3
+ # Just for object type testing
4
+ #
5
+ # These are defined in the oinky gem too. However, we declare them here
6
+ # also so that the gem is not required for compilation.
7
+ module Table
8
+ end
9
+ module Schema
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,152 @@
1
+ # This source is distributed under the terms of the MIT License. Refer
2
+ # to the 'LICENSE' file for details.
3
+ #
4
+ # Copyright (c) Jacob Lacouture, 2012
5
+
6
+ require 'ffi'
7
+
8
+ class NativeMallocBuffer
9
+ end
10
+
11
+ # This is intended to be used by multiple libraries to share large string
12
+ # objects, without having to copy them on/off the ruby heap, via
13
+ # ruby String.
14
+ #
15
+ # Some ruby implementations (JRuby) move GC memory around, rather than just
16
+ # refcounting it, as MRI does. To keep the string from moving, we need to
17
+ # allocate it on the native heap. However, we still want to manage it, as
18
+ # we want to be abel to share it between components, none of which may be
19
+ # capable of managing it independently.
20
+ #
21
+ # So, we use the ruby GC to manage an object, which explicitly manages the
22
+ # lifetime of the native object. The native object can be passed between
23
+ # components by reference, rather than always by value.
24
+ module NativeBuffer
25
+ include Comparable
26
+
27
+ module LibC
28
+ extend FFI::Library
29
+ ffi_lib FFI::Library::LIBC
30
+
31
+ attach_function :malloc, [:size_t], :pointer
32
+ attach_function :free, [:pointer], :void
33
+ attach_function :memcpy, [:pointer, :pointer, :size_t], :void
34
+ attach_function :memcmp, [:pointer, :pointer, :size_t], :int
35
+ end
36
+
37
+ # supports the following methods:
38
+
39
+ # String length in bytes (not chars)
40
+ # length/size
41
+
42
+ # Returns an FFI pointer
43
+ # def ptr
44
+
45
+ # converts to a ruby string.
46
+ # def to_s
47
+
48
+ # Implementations of this module should supply these two definitions.
49
+
50
+ # def ptr
51
+ # @ptr
52
+ # end
53
+ # def length
54
+ # @size
55
+ # end
56
+
57
+ def clone
58
+ NativeMallocBuffer.new(self)
59
+ end
60
+
61
+ # Convert native string to a ruby string
62
+ def rb_str
63
+ l = self.length
64
+ return '' unless l > 0
65
+ self.ptr.read_string(l)
66
+ end
67
+
68
+ alias :to_s :rb_str
69
+
70
+ def inspect
71
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} length=#{self.length} ptr=#{self.ptr.inspect}"
72
+ end
73
+
74
+ def each_byte
75
+ e = Enumerator.new { |blk|
76
+ (0..self.length-1).each { |i|
77
+ blk.yield (self.ptr + i).read_uchar
78
+ }
79
+ }
80
+ if block_given?
81
+ e.each {|v| yield v}
82
+ return self
83
+ else
84
+ return e
85
+ end
86
+ end
87
+
88
+ def each_char
89
+ if block_given?
90
+ each_byte { |b| yield(b.chr) }
91
+ return self
92
+ else
93
+ return Enumerator.new { |blk|
94
+ each_byte { |b| blk.yield(b.chr) }
95
+ }
96
+ end
97
+ end
98
+
99
+ def <=>(other)
100
+ if other.is_a? NativeBuffer
101
+ ll = self.length
102
+ ol = other.length
103
+ k = ll - ol
104
+ cl = k < 0 ? ll : ol
105
+ r = LibC.memcmp(self.ptr, other.ptr, cl)
106
+ return r if r != 0
107
+ return k
108
+ else
109
+ return self.to_s <=> other
110
+ end
111
+ end
112
+ end
113
+
114
+ # This is a specific implementation of NativeBuffer. It uses malloc/free.
115
+ class NativeMallocBuffer
116
+ include NativeBuffer
117
+
118
+ attr_reader :ptr, :length
119
+
120
+ def initialize(v = 0)
121
+ if (v.is_a? Fixnum)
122
+ @ptr = FFI::MemoryPointer.new :pointer
123
+ size = v
124
+ if size > 0
125
+ @ptr = LibC.malloc(size)
126
+ @length = size
127
+ else
128
+ @ptr = nil
129
+ @length = 0
130
+ end
131
+ elsif v.is_a? String
132
+ size = v.length
133
+ @ptr = LibC.malloc(size)
134
+ LibC.memcpy(@ptr, v, size)
135
+ @length = size
136
+ elsif v.is_a? NativeBuffer
137
+ size = v.length
138
+ @ptr = LibC.malloc(size)
139
+ LibC.memcpy(@ptr, v.ptr, size)
140
+ @length = size
141
+ else
142
+ raise OinkyException.new("Invalid initialize parameter to NativeMallocBuffer.new")
143
+ end
144
+
145
+ ObjectSpace.define_finalizer( self, self.class.finalize(@ptr) )
146
+ end
147
+
148
+ def self.finalize(ptr)
149
+ proc { LibC.free(ptr) }
150
+ end
151
+ end
152
+
@@ -0,0 +1,132 @@
1
+ require 'date'
2
+
3
+ module Oinky
4
+ module Model
5
+
6
+ DefaultsByType = {
7
+ :string=>'',
8
+ :bit=>false,
9
+ :int8=>0,
10
+ :int16=>0,
11
+ :int32=>0,
12
+ :int64=>0,
13
+ :uint8=>0,
14
+ :uint16=>0,
15
+ :uint32=>0,
16
+ :uint64=>0,
17
+ :float32=>0.0,
18
+ :float64=>0.0,
19
+ :variant=>0,
20
+ :datetime=>DateTime.parse("0001-01-01T00:00:00 UTC")
21
+ }
22
+
23
+ def self.validate_keys(h, keys)
24
+ h.keys.each{|k,v|
25
+ unless keys.find_index(k)
26
+ raise ArgumentError.new("Unrecognized key in Oinky schema " +
27
+ "definition: #{k}")
28
+ end
29
+ }
30
+ end
31
+ def self.validate_type(t)
32
+ unless DefaultsByType.has_key?(t)
33
+ raise ArgumentError.new("Unrecognized type in Oinky schema definition: #{k}")
34
+ end
35
+ end
36
+
37
+ def self.normalize_column_def(nm, v)
38
+ if v.is_a? Symbol
39
+ validate_type(v)
40
+ return {:type=>v, :default=>DefaultsByType[v], :accessor=>nm}
41
+ end
42
+ # Otherwise v is a hash
43
+ validate_keys(v,[:type,:default,:accessor])
44
+ t = v[:type]
45
+ raise ArgumentError.new("No column type specified for column [#{nm}]") unless t
46
+ v[:default] = DefaultsByType[t] unless v.has_key?(:default)
47
+ # the accessor is only used for some target languages
48
+ v[:accessor] = nm unless v.has_key?(:accessor)
49
+ return v
50
+ end
51
+
52
+ def self.normalize_index_column_def(cd)
53
+ if cd.is_a?(Symbol)
54
+ cd = cd.to_s
55
+ end
56
+ if cd.is_a?(String)
57
+ # ascending is the default
58
+ return {:name=>cd, :ascending=>true}
59
+ end
60
+ cd[:ascending] = true unless cd.has_key?(:ascending)
61
+ validate_keys(cd,[:name,:ascending])
62
+ return cd
63
+ end
64
+
65
+ def self.normalize_index_def(k,v)
66
+ validate_keys(v,[:unique,:columns,:accessor, :name])
67
+ if v.has_key?(:name) and (k.to_s != v[:name].to_s)
68
+ raise ArgumentError.new("Inconsistent names for index: [#{k}] and [#{v[:name]}].")
69
+ end
70
+ d = {}
71
+ d[:unique] = (v[:unique] or false)
72
+ d[:accessor] = (v[:accessor] or k.to_s)
73
+ d[:columns] = v[:columns].map{|cd|
74
+ normalize_index_column_def(cd)
75
+ }
76
+ return d
77
+ end
78
+
79
+ def self.normalize_table_schema(nm, h)
80
+ cols = {}
81
+ # columns is a hash of column_name=>defn
82
+ h[:columns].each{|k,v|
83
+ cn = k.to_s
84
+ cols[cn] = normalize_column_def(cn, v)
85
+ }
86
+
87
+ ixs = {}
88
+ h[:indices].each{|k,v|
89
+ ixs[k.to_s] = normalize_index_def(k,v)
90
+ }
91
+
92
+ if h.has_key?(:name) and (nm.to_s != h[:name].to_s)
93
+ raise ArgumentError.new("Inconsistent names for table: [#{nm}] and [#{h[:name]}].")
94
+ end
95
+
96
+ return {
97
+ :name=>nm.to_s,
98
+ :accessor=>(h[:accessor] or nm).to_s,
99
+ :columns=>cols,
100
+ :indices=>ixs
101
+ }
102
+ end
103
+
104
+ def self.normalize_schema(s)
105
+ validate_keys(s,[:name, :tables, :version, :classname])
106
+ raise ArgumentError.new("Invalid schema definition") unless
107
+ s[:name] and s[:name].size and s[:tables] and s[:version]
108
+
109
+ tbls = {}
110
+ if s[:tables].is_a? Hash
111
+ s[:tables].each{|k,v|
112
+ tbls[k.to_s] = normalize_table_schema(k.to_s, v)
113
+ }
114
+ else
115
+ s[:tables].each{|v|
116
+ nm = v[:name].to_s
117
+ tbls[nm] = normalize_table_schema(nm, v)
118
+ }
119
+ end
120
+
121
+ extras = {}
122
+ extras[:classname] = s[:classname] if s.has_key?(:classname)
123
+
124
+ return {
125
+ :name=>s[:name].to_s,
126
+ :tables=>tbls,
127
+ :version=>s[:version]
128
+ }.merge(extras)
129
+ end
130
+
131
+ end #module Model
132
+ end #module Oinky
@@ -0,0 +1,44 @@
1
+ # This source is distributed under the terms of the MIT License. Refer
2
+ # to the 'LICENSE' file for details.
3
+ #
4
+ # Copyright (c) Jacob Lacouture, 2012
5
+
6
+
7
+ module Oinky
8
+ module Detail
9
+ class Builder
10
+ include Enumerable
11
+
12
+ def initialize(lwidth = 4, lcount = 0)
13
+ @lwidth = lwidth
14
+ @lcount = lcount
15
+ @str = []
16
+ end
17
+ def next(s1, s2)
18
+ self << s1
19
+ @lcount += 1
20
+ yield
21
+ @lcount -= 1
22
+ self << s2
23
+ self
24
+ end
25
+ def <<(s)
26
+ write(s, 0)
27
+ end
28
+ def write(s, ldelta)
29
+ l = @lcount + ldelta
30
+ @str << ((' ' * l * @lwidth) + s)
31
+ self
32
+ end
33
+
34
+ def format
35
+ return (@str * "\n") + "\n"
36
+ end
37
+
38
+ def each
39
+ return @str.each
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,193 @@
1
+ # This source is distributed under the terms of the MIT License. Refer
2
+ # to the 'LICENSE' file for details.
3
+ #
4
+ # Copyright (c) Jacob Lacouture, 2012
5
+
6
+ require 'oinky/error'
7
+
8
+ # This is the crude skeleton of basic query operations. Currently
9
+ # This works by returning enhanced enumerator objects.
10
+
11
+ module Oinky
12
+ # This adds simple operations (max/min/average) to enumerable value sets.
13
+ class ValuesEnumerator < Enumerator
14
+ def self.from_opt(opt)
15
+ # These are column references.
16
+ if opt.is_a? Symbol
17
+ opt = opt.to_s
18
+ end
19
+ if opt.is_a? String
20
+ # Turn this into a proc that extracts the selected value.
21
+ cn = opt
22
+ opt = lambda {|row| row[cn]}
23
+ end
24
+
25
+ raise OinkyException.new("ArgumentError - Invalid proc") unless opt.is_a? Proc
26
+ return opt
27
+ end
28
+ private
29
+ def values_loop(opt)
30
+ # This is a function object which will map the row to a value that
31
+ # we will compute the average of
32
+ opt = self.class.from_opt(opt)
33
+ begin
34
+ while true
35
+ v = self.next
36
+ val = opt.call(v)
37
+ raise OinkyException.new("nil result from functor") unless val
38
+ yield val
39
+ end
40
+ rescue StopIteration => e
41
+ end
42
+ end
43
+
44
+ public
45
+ def average(opt)
46
+ avg = nil
47
+ count = 0
48
+ values_loop(opt) { |val|
49
+ if avg
50
+ avg += val
51
+ else
52
+ avg = val
53
+ end
54
+ count += 1
55
+ }
56
+ # We do not test for zero elements. We just invoke the value's divide
57
+ # method. The caller can use any type, and define divide however
58
+ # they choose.
59
+ return avg / count
60
+ end
61
+ def max(opt)
62
+ mx = nil
63
+ values_loop(opt) { |val|
64
+ if not mx
65
+ mx = val
66
+ elsif mx < val
67
+ mx = val
68
+ end
69
+ }
70
+ return mx
71
+ end
72
+ def min(opt)
73
+ mn = nil
74
+ values_loop(opt) { |val|
75
+ if not mn
76
+ mn = val
77
+ elsif mn > val
78
+ mn = val
79
+ end
80
+ }
81
+ return mn
82
+ end
83
+ end
84
+
85
+ # Add dataset filtering to the rowset (applies to both tables and indices)
86
+ # The indexes are not used autmatically. An index is only used if the
87
+ # filter is applied to an index using positional specification.
88
+
89
+ module RowSet
90
+ def __each_filtered(c,terminate,filter)
91
+ Enumerator.new {|blk|
92
+ if c.is_valid?
93
+ v = c.select_all
94
+ while true
95
+ blk.yield v if filter.call(v)
96
+ c.seek_next
97
+ break unless c.is_valid?
98
+ v = c.select_all
99
+ break if terminate and terminate.call(v)
100
+ end
101
+ end
102
+ }
103
+ end
104
+ def __make_filter_proc(f)
105
+ if [String,Integer,Fixnum,Float,DateTime].find_index(f.class)
106
+ return lambda{|v| v == f}
107
+ end
108
+ if f.is_a? Proc
109
+ return f
110
+ end
111
+ raise ArgumentError.new("Hash value must be proc or value.")
112
+ end
113
+ def __rs_filter(f,c,t)
114
+ if f.is_a? Hash
115
+ p = {}
116
+ cols = @table.columns
117
+ f.each{|k,kf|
118
+ unless cols[k]
119
+ raise ArgumentError.new("Unknown column name in hash filter.")
120
+ end
121
+ p[k] = __make_filter_proc(kf)
122
+ }
123
+ f = lambda {|v|
124
+ r = true
125
+ p.each{|k,kf|
126
+ r &&= kf.call(v[k])
127
+ }
128
+ }
129
+ end
130
+ unless f.is_a? Proc
131
+ raise ArgumentError.new("Invalid argument to filter. Need Proc or Hash.")
132
+ end
133
+
134
+ seq = __each_filtered(c,t,f)
135
+ if block_given?
136
+ seq.each {|v|
137
+ yield v
138
+ }
139
+ return self
140
+ else
141
+ return seq
142
+ end
143
+ end
144
+ def filter(f)
145
+ __rs_filter(f,new_cursor(),nil)
146
+ end
147
+ end #module RowSet
148
+
149
+ class Index
150
+ # Specialize the rowset filter to add specification by array(position)
151
+ def filter(f)
152
+ if f.is_a? Array
153
+ c = new_cursor().seek(f)
154
+ rg = f
155
+ cols = self.columns
156
+ if f.size > cols.size
157
+ raise ArgumentError.new("Position specification exceeds index width.")
158
+ end
159
+ # truncate our test width to what we were given
160
+ cols = cols[0..f.size-1]
161
+ # Use an array for indirection in the first parameter, so
162
+ # the two lambdas can share state.
163
+ result = [true]
164
+ f = lambda {|v|
165
+ r = true
166
+ cols.each_with_index{|cn,i|
167
+ if rg[i].is_a? Proc
168
+ r &&= rg[i].call(v[cn])
169
+ else
170
+ r &&= (v[cn] == rg[i])
171
+ end
172
+ }
173
+ result[0] = r
174
+ r
175
+ }
176
+ result[0] = c.is_valid? && f.call(c.select_all)
177
+ # We stop on the first rejection
178
+ done = lambda { |c| !result[0]}
179
+ else
180
+ c = new_cursor().seek_first()
181
+ done = nil
182
+ end
183
+ e = __rs_filter(f,c,done)
184
+ if block_given?
185
+ e.each {|v| yield v}
186
+ return self
187
+ else
188
+ return e
189
+ end
190
+ end
191
+ end
192
+ end #module Oinky
193
+