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,106 @@
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
+ # This is a duplicate definition. It's here to decouple the compiler
8
+ # from the oinky gem. The gem requires building a native extension,
9
+ # whereas the compiler doesn't. This way the compiler can be run
10
+ # right out of the source directory without build/install.
11
+ # Consistency of these definitions is assured in the Oinky gem.
12
+ column_types = [:variant, :bit,
13
+ :int8, :int16, :int32, :int64,
14
+ :uint8, :uint16, :uint32, :uint64,
15
+ :float32, :float64,
16
+ :datetime, :string].sort
17
+ ColumnTypes ||= column_types
18
+
19
+ # ColumnTypes may have been previously defined (by the gem)
20
+ # but if so, the definition should have been identical.
21
+ unless ColumnTypes == column_types
22
+ raise OinkyException.new("Mismatched column types definition: " +
23
+ "#{ColumnTypes.inspect} != #{column_types.inspect}")
24
+ end
25
+ end
26
+
27
+ require 'oinky/oc_builder'
28
+ # Just preload all the languages we know about. They are well namespaced
29
+ # so there's no harm.
30
+ require 'oinky/cpp_emitter'
31
+ require 'oinky/rb_emitter'
32
+ require 'oinky/modelbase'
33
+ require 'oinky/dsl'
34
+
35
+ # This is the oinky model compiler. Given a model specification, it
36
+ # can generate a set of accessor classes and basic migration routines
37
+ # in a target language.
38
+ #
39
+ # The purpose of this compiler is to permit the model to be defined
40
+ # exactly once, in one language. It can generate code in any language.
41
+ # There should be NO REASON WHATSOEVER to EVER MODIFY THE GENERATED CODE!!!
42
+ # That is always the wrong thing to do. Alter the generator if need be.
43
+
44
+ module Oinky
45
+ module Model
46
+ class DerivedType
47
+ def initialize(basetype, instancetype)
48
+ @basetype = basetype
49
+ @instancetype = instancetype
50
+ end
51
+ def instance_type(lang)
52
+ return @instancetype if @instancetype.is_a? String
53
+ return @instancetype[lang]
54
+ end
55
+ def oinky_type
56
+ return @basetype if @basetype.is_a? Symbol
57
+ return @basetype.oinky_type
58
+ end
59
+ # This can be overriden by any derived type. By default
60
+ # we just build from the base type.
61
+ def value_from_variant(src_exp, emitter)
62
+ emitter.value_from_variant(src_exp, oinky_type)
63
+ end
64
+ end
65
+
66
+ def self.generate(schema, ccls)
67
+ # Allow specification by class or symbol
68
+ ccls = ({:cpp=>Cpp, :CPP=>Cpp, :ruby=>Ruby, :rb=>Ruby}[ccls] || ccls)
69
+ ccls.new(schema).emit
70
+ end
71
+ end
72
+ end
73
+
74
+ =begin
75
+
76
+ class TableDefinition
77
+ def initialize(h)
78
+ @h = h
79
+ end
80
+
81
+ attr_reader :h
82
+ end
83
+
84
+ def self.define_table(h)
85
+ h[:indices] = (h[:indices] || h[:indexes] || [])
86
+ TableDefinition.new(h)
87
+ end
88
+
89
+ class Table
90
+
91
+ end
92
+
93
+ class Model
94
+ class << self
95
+
96
+ def create_table(tablename)
97
+
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ =end
104
+
105
+ require 'oinky/cpp_emitter'
106
+
@@ -0,0 +1,311 @@
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/normalize.rb'
7
+
8
+ module Oinky
9
+ module Model
10
+ class Cpp
11
+ def initialize(schema)
12
+ @schema = Oinky::Model.normalize_schema(schema)
13
+ end
14
+
15
+ def cppify(str)
16
+ str.gsub(/($[0-9])|[^a-z0-9A-Z_]/,'_')
17
+ end
18
+
19
+ def schemaname_to_ns(str) ; cppify(str) ; end
20
+ def tablename_to_groupname(str) ; cppify(str) ; end
21
+ def handle_from_tablename(str) ; "#{cppify(str)}_handle" ; end
22
+ def accessor_from_tablename(str) ; cppify(str) ; end
23
+
24
+ def make_datetime_expression(d)
25
+ "Oinky::datetime_t::compose(#{d.year-1900},#{d.month - 1},#{d.day - 1}," +
26
+ "#{d.hour},#{d.minute},#{d.second},#{(d.second_fraction * 1000000).to_i})"
27
+ end
28
+
29
+ def default_value_expression(coldef)
30
+ d = coldef[:default]
31
+ return "#{instance_type_expression(coldef[:type])}()" unless d
32
+ return d.inspect if d.is_a? String
33
+ return d.inspect if d.is_a? Fixnum
34
+ return make_datetime_expression(d) if d.is_a? DateTime
35
+ return d[:cpp]
36
+ end
37
+
38
+ def accessor_name(h)
39
+ return cppify(h[:accessor])
40
+ end
41
+
42
+ def index_column_name(col)
43
+ return col if col.is_a? String
44
+ return col.to_s if col.is_a? Symbol
45
+ return index_column_name(col[:name])
46
+ end
47
+ def index_column_ascending(col)
48
+ return col[:ascending] if col.is_a? Hash
49
+ # ascending is the default
50
+ return true
51
+ end
52
+
53
+ def instance_type_expression(type)
54
+ self.class.instance_type_expression(type)
55
+ end
56
+ def self.instance_type_expression(type)
57
+ case type
58
+ when :variant
59
+ return 'Oinky::variant_cv_t'
60
+ when :bit
61
+ return 'bool'
62
+ when :datetime
63
+ return 'Oinky::datetime_t'
64
+ when :string
65
+ return 'Oinky::db_string'
66
+ #int
67
+ when :int8
68
+ return 'int8_t'
69
+ when :int16
70
+ return 'int16_t'
71
+ when :int32
72
+ return 'int32_t'
73
+ when :int64
74
+ return 'int64_t'
75
+ #uint
76
+ when :uint8
77
+ return 'uint8_t'
78
+ when :uint16
79
+ return 'uint16_t'
80
+ when :uint32
81
+ return 'uint32_t'
82
+ when :uint64
83
+ return 'uint64_t'
84
+ #float
85
+ when :float32
86
+ return 'float32_t'
87
+ when :float64
88
+ return 'float64_t'
89
+ else
90
+ # This must be a derived type. It should support this method, to
91
+ # give us an expression we can use in the target language.
92
+ return type.instance_type(self)
93
+ end
94
+ end
95
+
96
+ def value_from_variant(src,type)
97
+ self.class.value_from_variant(src,type)
98
+ end
99
+ def self.value_from_variant(src,type)
100
+ case type
101
+ when :variant
102
+ return src
103
+ when :bit
104
+ return "#{src}.bit_value()"
105
+ when :datetime
106
+ return "#{src}.dt_value()"
107
+ when :string
108
+ return "#{src}.string_value()"
109
+ #int
110
+ when :int8
111
+ return "(int8_t) #{src}.int_value()"
112
+ when :int16
113
+ return "(int16_t) #{src}.int_value()"
114
+ when :int32
115
+ return "(int32_t) #{src}.int_value()"
116
+ when :int64
117
+ return "#{src}.int_value()"
118
+ #uint
119
+ when :uint8
120
+ return "(uint8_t) #{src}.uint_value()"
121
+ when :uint16
122
+ return "(uint16_t) #{src}.uint_value()"
123
+ when :uint32
124
+ return "(uint32_t) #{src}.uint_value()"
125
+ when :uint64
126
+ return "#{src}.uint_value()"
127
+ #float
128
+ when :float32
129
+ return "#{src}.f32_value()"
130
+ when :float64
131
+ return "#{src}.f64_value()"
132
+
133
+ else
134
+ # This must be a derived type. It should support this method, to
135
+ # give us an expression we can use in the target language.
136
+ return type.value_from_variant(src, self)
137
+ end
138
+ end
139
+
140
+ def type_code(h)
141
+ self.class.type_code(h)
142
+ end
143
+ def self.type_code(h)
144
+ h = h[:type].to_s
145
+ h[0] = h[0].upcase
146
+ return 'column_types::' + h
147
+ end
148
+
149
+ def make_index_name(idef)
150
+ cppify(idef[:name])
151
+ end
152
+ def make_index_handle_name(idef)
153
+ "_idx_handle__#{make_index_name(idef)}"
154
+ end
155
+ def make_table_classname(tn)
156
+ "CTable_#{cppify(tn)}"
157
+ end
158
+
159
+ def indices(t)
160
+ t[:indices] || t[:indexes] || []
161
+ end
162
+
163
+ #Generate a string, containing the C++ code for the model classes.
164
+ def emit
165
+ p = Oinky::Detail::Builder.new
166
+ ns = "namespace #{schemaname_to_ns(@schema[:name])}"
167
+ p.next("#{ns} {", "} //#{ns}") {
168
+ p << "using namespace Oinky;"
169
+ p << "using Oinky::Model::TableBase;"
170
+ p << "using Oinky::Model::SchemaBase;"
171
+ p << "using Oinky::db_t;"
172
+ p << ''
173
+ p << "class Schema;"
174
+ tables = @schema[:tables]
175
+ tables.each{|tn, t|
176
+ # struct_hdr = "struct #{tablename_to_groupname(t[:name])}"
177
+ # p << struct_hdr
178
+ # p.next("{", "}; //#{struct_hdr}") {
179
+ tbl_classname = make_table_classname(tn)
180
+ p << ''
181
+ p << "class #{tbl_classname} : public TableBase"
182
+ p.next("{", "}; //class #{tbl_classname}") {
183
+ p << "friend class Schema;"
184
+ p.write("public:", -1)
185
+ p.next("struct Row {", "}; //struct Row") {
186
+ maxw = t[:columns].max_by{|k,col| instance_type_expression(col[:type]).length}
187
+ maxwlen = maxw ? instance_type_expression(maxw[1][:type]).length : 0
188
+ t[:columns].each{|cn,col|
189
+ if col[:description]
190
+ col[:description].split("\n").each{|l|
191
+ p << ("// " + l)
192
+ }
193
+ end
194
+ # The column name is the default value of the accessor name,
195
+ # but it can be overridden
196
+ p << Kernel.sprintf("%-#{maxwlen}s %s;",
197
+ instance_type_expression(col[:type]),
198
+ accessor_name(col))
199
+ }
200
+ }
201
+ p << ''
202
+ p.write("private:", -1)
203
+ p << "typedef db_t::column_selector_t column_selector_t;"
204
+ p << 'column_selector_t row_selector;'
205
+ # index handles
206
+ indices(t).each {|iname, idef|
207
+ p << "const db_t::index_handle #{accessor_name(idef)};"
208
+ }
209
+ p << ''
210
+ # update_schema
211
+ p << 'static void up_schema(db_t::table_handle th)'
212
+ p.next('{','}') {
213
+ # Add columns
214
+ t[:columns].each{|cn, col|
215
+ p << "TableBase::create_column_if(th, #{cn.inspect}, #{type_code(col)}, #{default_value_expression(col)});"
216
+ }
217
+ # Add indices
218
+ indices(t).each_with_index {|idef,idx|
219
+ iname, idef = idef
220
+ # Column definitions
221
+ names = "idx_column_defs_#{idx}"
222
+ columns = idef[:columns]
223
+ p.next("static index_column_def #{names}[] = {", "};") {
224
+ columns.each_with_index{|col, i|
225
+ colname = index_column_name(col)
226
+ ascending = index_column_ascending(col) ? 'true' : 'false'
227
+ sep = (i == 0 ? '' : ',')
228
+ p << "#{sep} { db_string(\"#{colname}\"), #{ascending} }"
229
+ }
230
+ }
231
+ unique = idef[:unique] ? 'true' : 'false'
232
+ p << "TableBase::create_index_if(th, #{iname.inspect}, #{unique}, #{names}, #{names}+#{columns.size} );"
233
+ }
234
+ }
235
+ p << ''
236
+ p << "template<typename CURSOR>"
237
+ p.next("void priv_row_from_cursor(Row &row, const CURSOR &crs) {", "}") {
238
+ # Don't presume that enumeration with index is a consistent
239
+ # ordering
240
+ indexed_cols = []
241
+ p.next("static const char *column_names[] = {","};") {
242
+ t[:columns].each{|cn, col|
243
+ p << "#{cn.inspect},"
244
+ indexed_cols << col
245
+ }
246
+ p << 'NULL'
247
+ }
248
+ p.next("TableBase::check_init_row_accessor(", "") {
249
+ p << "row_selector, "
250
+ p << "column_names,"
251
+ p << "column_names + #{t[:columns].count}"
252
+ p << ");"
253
+ }
254
+ p.next("variant_cv_t *vals = (variant_cv_t *) ","") {
255
+ p << "alloca(sizeof(variant_cv_t) * #{t[:columns].count});"
256
+ }
257
+ p << "crs.select(row_selector).copy_to(vals, #{t[:columns].count});"
258
+ p << "// Select values into the target struct"
259
+ indexed_cols.each_with_index{|col,i|
260
+ val = value_from_variant("vals[#{i}]", col[:type])
261
+ p << "row.#{accessor_name(col)} = #{val};"
262
+ }
263
+ } # priv_row_from_cursor
264
+ p.write("public:", -1)
265
+ # Constructor - initialize index handles.
266
+ p.next("#{tbl_classname}(db_t::table_handle _th, db_t &db) : TableBase(_th, db)","{}") {
267
+ indices(t).each {|iname, idef|
268
+ p << ",#{accessor_name(idef)}(TableBase::get_index_handle(_th, \"#{iname}\"))"
269
+ }
270
+ }
271
+ # Row accessors
272
+ p << ''
273
+ p.next("void row_from_cursor(Row &row, const db_t::index_cursor_handle &crs) {", "}") {
274
+ p << "priv_row_from_cursor(row, crs);"
275
+ }
276
+ p.next("void row_from_cursor(Row &row, const db_t::table_cursor_handle &crs) {", "}") {
277
+ p << "priv_row_from_cursor(row, crs);"
278
+ }
279
+ } # class "#{tbl_classname}"
280
+ } # tables.each {}
281
+
282
+ p << ''
283
+ p << "class Schema : public SchemaBase"
284
+ p.next("{","}; //class Schema") {
285
+ p.next("static void up_schema(db_t &db) {","}") {
286
+ tables.each{|tn, t|
287
+ tbl_classname = make_table_classname(tn)
288
+ p << "#{tbl_classname}::up_schema(SchemaBase::get_table_handle(db, #{tn.inspect}));";
289
+ }
290
+ }
291
+ p << ''
292
+ p.write("public:", -1)
293
+ # Table accessors
294
+ tables.each {|tn, tdef|
295
+ tbl_classname = make_table_classname(tn)
296
+ p << "const #{tbl_classname} #{accessor_name(tdef)};"
297
+ }
298
+ p << ''
299
+ p.next("Schema(db_t &db) : SchemaBase(db, &up_schema)","{}") {
300
+ tables.each {|tn, tdef|
301
+ p << ",#{accessor_name(tdef)}(SchemaBase::get_table_handle(db, #{tn.inspect}), db)"
302
+ }
303
+ }
304
+ }
305
+ } # namespace <schemaname>
306
+ return p.format
307
+ end
308
+ end #class Cpp
309
+ end #module Model
310
+ end
311
+
@@ -0,0 +1,167 @@
1
+ # We don't strictly need to normalize here. We'll do it
2
+ # again in the emit phase. However, normalize is also a
3
+ # syntax checker, so doing it early, as we're building the
4
+ # schema, makes error discovery more local and easier to diagnose.
5
+
6
+ require 'oinky/normalize'
7
+
8
+ module Oinky
9
+ module Model
10
+ module Internal
11
+ class IndexBuilder
12
+ def initialize(name, base = {})
13
+ @h = {
14
+ :name=>name.to_s,
15
+ :columns=>(base[:columns] or []).clone,
16
+ :unique=>(base[:unique] or false)
17
+ }
18
+ end
19
+
20
+ def add_column(colname, ascending = true)
21
+ if colname.is_a? Hash
22
+ h = colname
23
+ else
24
+ h = {:name=>colname.to_s, :ascending=>ascending}
25
+ end
26
+ @h[:columns] << Oinky::Model.normalize_index_column_def(h)
27
+ end
28
+
29
+ def set_unique(val)
30
+ @h[:unique] = (val ? true : false)
31
+ end
32
+
33
+ def to_h
34
+ @h
35
+ end
36
+
37
+ # override name
38
+ def name(newname)
39
+ @h[:name] = newname.to_s
40
+ end
41
+ end
42
+
43
+ class TableBuilder
44
+ def initialize(base = {})
45
+ base = {:columns=>{}, :indices=>{}}.merge(base)
46
+ @h = {:columns=>base[:columns].clone, :indices=>base[:indices].clone}
47
+ end
48
+ Oinky::ColumnTypes.each {|ct|
49
+ define_method(ct) { |colname, h = {}|
50
+ add_column(colname, ct, h)
51
+ }
52
+ }
53
+ def add_column(colname, type, h = {})
54
+ v = {:type=>type}.merge(h)
55
+ k = colname.to_s
56
+ v = Oinky::Model.normalize_column_def(k,v)
57
+ @h[:columns][k] = v
58
+ end
59
+ def add_index(name, id = {:name=>name, :unique=>true, :columns=>[]})
60
+ k = name
61
+ if block_given?
62
+ s = IndexBuilder.new(name, id)
63
+ s.instance_eval &Proc.new
64
+ id = s.to_h
65
+ k = id[:name]
66
+ end
67
+ id = Oinky::Model.normalize_index_def(k,id)
68
+ id.delete(:name)
69
+ @h[:indices][k] = id
70
+ end
71
+ def to_h
72
+ @h
73
+ end
74
+
75
+ # override name
76
+ def name(newname)
77
+ @h[:name] = newname.to_s
78
+ end
79
+ def accessor(a)
80
+ @h[:accessor] = a.to_s
81
+ end
82
+ end
83
+
84
+ class SchemaBuilder
85
+ def initialize(name, base = {})
86
+ @h = {}
87
+ name = name.to_s
88
+ # clone 2 levels deep. tabledef, not just tableset
89
+ base.each{|k,v|
90
+ if v.is_a? Hash
91
+
92
+ nv = {}
93
+ v.each{|k2,v2|
94
+ nv[k2] = v2.clone
95
+ }
96
+ @h[k] = nv
97
+ else
98
+ @h[k] = v
99
+ end
100
+ }
101
+ @h[:name] = name
102
+ @h[:version] ||= 1
103
+ @h[:tables] ||= {}
104
+ end
105
+
106
+ def create_table(tn, base = {})
107
+ if tn.is_a? Hash
108
+ base = tn
109
+ tn = tn[:name]
110
+ end
111
+ tn = tn.to_s
112
+ t = TableBuilder.new(base)
113
+ t.name tn
114
+ if block_given?
115
+ t.instance_eval &Proc.new
116
+ end
117
+ th = t.to_h
118
+ name = th[:name]
119
+ th = Oinky::Model.normalize_table_schema(name, th)
120
+ @h[:tables][name] = th
121
+ th
122
+ end
123
+
124
+ # override name
125
+ def name(newname)
126
+ @h[:name] = newname.to_s
127
+ end
128
+
129
+ def version(v)
130
+ @h[:version] = v
131
+ end
132
+
133
+ def classname(cn)
134
+ @h[:classname] = cn
135
+ end
136
+
137
+ def to_h
138
+ @h
139
+ end
140
+ end #module SchemaBuilder
141
+ end #module Internal
142
+
143
+ # Build an entire schema
144
+ def self.build_schema(name, base = {}, &b)
145
+ s = Internal::SchemaBuilder.new(name, base)
146
+ s.instance_eval &b
147
+ return s.to_h
148
+ end
149
+
150
+ # Build an entire schema, generate a ruby adapter class, and evaluate it
151
+ def self.build_schema_adapter(name, base = {}, &b)
152
+ s = build_schema(name, base, &b)
153
+ cls = eval Oinky::Model.generate(s, :ruby)
154
+ return cls
155
+ end
156
+
157
+ # Someone may want to create an unbound table-def and then reuse
158
+ # it later in multiple schemas, or as a base definition for derived
159
+ # tables.
160
+ def self.build_table(name, base = {})
161
+ s = Internal::TableBuilder.new(base)
162
+ s.name(name)
163
+ s.instance_eval &Proc.new
164
+ s.to_h
165
+ end
166
+ end # Model
167
+ end # Oinky