oinky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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