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,158 @@
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
+ #include "nky_lib_core.hpp"
7
+
8
+
9
+ // Create a cursor at the beginning of the index.
10
+ static oinky_error oinky_index_cursor_new_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t **crs)
11
+ {
12
+ db_t::index_handle ih(make_handle(index));
13
+ db_t::index_cursor_handle cnew(ih.new_cursor(ih.begin()));
14
+ *crs = (oinky_index_row_cursor_t *) cnew.raw();
15
+ return nky_success;
16
+ }
17
+ oinky_error oinky_index_cursor_new(oinky_index_handle_t *index, oinky_index_row_cursor_t **crs)
18
+ {
19
+ return wrap_exception(boost::bind(oinky_index_cursor_new_impl, index, crs));
20
+ }
21
+
22
+ static oinky_error oinky_index_cursor_clone_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *src, oinky_index_row_cursor_t **newcrs)
23
+ {
24
+ db_t::index_cursor_handle cs(make_handle(src));
25
+ db_t::index_cursor_handle cnew(cs.clone());
26
+ *newcrs = (oinky_index_row_cursor_t *) cnew.raw();
27
+ return nky_success;
28
+ }
29
+ oinky_error oinky_index_cursor_clone(oinky_index_handle_t *index, oinky_index_row_cursor_t *src, oinky_index_row_cursor_t **newcrs)
30
+ {
31
+ return wrap_exception(boost::bind(oinky_index_cursor_clone_impl, index, src, newcrs));
32
+ }
33
+
34
+ void oinky_index_cursor_free(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
35
+ {
36
+ // This deletes the internal cursor object.
37
+ // ***IMPORTANT***
38
+ // 1) It can only be called once.
39
+ // 2) It must be called before the DB is destroyed or not at all. Cursor
40
+ // lifetimes are bounded by DB lifetimes by definition, so DB destroy
41
+ // implicitly frees all cursors.
42
+ db_t::index_cursor_handle c(make_handle(crs));
43
+ make_handle(index).delete_cursor(c);
44
+ }
45
+
46
+ static oinky_error oinky_index_cursor_seek_last_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
47
+ {
48
+ db_t::index_handle ih(make_handle(index));
49
+ db_t::index_cursor_handle c(make_handle(crs));
50
+ ih.move_cursor(c, ih.end());
51
+ c.seek_prev();
52
+ if (c.before_begin()) {
53
+ c.seek_next();
54
+ }
55
+ return nky_success;
56
+ }
57
+ oinky_error oinky_index_cursor_seek_last(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
58
+ {
59
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_last_impl, index, crs));
60
+ }
61
+
62
+ static oinky_error oinky_index_cursor_seek_first_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
63
+ {
64
+ db_t::index_handle ih(make_handle(index));
65
+ db_t::index_cursor_handle c(make_handle(crs));
66
+ ih.move_cursor(c, ih.begin());
67
+ return nky_success;
68
+ }
69
+ oinky_error oinky_index_cursor_seek_first(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
70
+ {
71
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_first_impl, index, crs));
72
+ }
73
+
74
+ // Moves the given cursor to a position in the index defined by the given set of values.
75
+ // lower_bound and upper_bound are equivalent to their STL meanings.
76
+ typedef db_t::index_row_iterator (db_t::index_handle::*ix_search_method)(const variant_cv_t *, const variant_cv_t *) const;
77
+ static oinky_error oinky_index_cursor_seek_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs, size_t seq_len, const oinky_db_variant *value_seq, ix_search_method m)
78
+ {
79
+ db_t::index_handle ih(make_handle(index));
80
+ variant_cv_t *values = (variant_cv_t *) alloca(sizeof(variant_cv_t) * seq_len);
81
+ for (size_t i=0;i<seq_len;++i) {
82
+ values[i] = nky_variant_xform(value_seq[i]);
83
+ }
84
+ db_t::index_row_iterator itr = (ih.*m)(values, values + seq_len);
85
+ db_t::index_cursor_handle c(make_handle(crs));
86
+ ih.move_cursor(c, itr);
87
+ return nky_success;
88
+ }
89
+
90
+ oinky_error oinky_index_cursor_seek_lower_bound(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs, size_t seq_len, const oinky_db_variant *value_seq)
91
+ {
92
+ ix_search_method m = &db_t::index_handle::lower_bound<const variant_cv_t *>;
93
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_impl, index, crs, seq_len, value_seq, m));
94
+ }
95
+
96
+ oinky_error oinky_index_cursor_seek_upper_bound(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs, size_t seq_len, const oinky_db_variant *value_seq)
97
+ {
98
+ ix_search_method m = &db_t::index_handle::upper_bound<const variant_cv_t *>;
99
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_impl, index, crs, seq_len, value_seq, m));
100
+ }
101
+ // If found, returns success and sets cursor to element. If not found,
102
+ // return ObjectNotFound and sets cursor to end (state == 1). Analogous
103
+ // to STL find.
104
+ oinky_error oinky_index_cursor_seek_exact(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs, size_t seq_len, const oinky_db_variant *value_seq)
105
+ {
106
+ ix_search_method m = &db_t::index_handle::find<const variant_cv_t *>;
107
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_impl, index, crs, seq_len, value_seq, m));
108
+ }
109
+ // increment/decrement
110
+ oinky_error oinky_index_cursor_seek_next_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
111
+ {
112
+ make_handle(crs).seek_next();
113
+ return nky_success;
114
+ }
115
+ oinky_error oinky_index_cursor_seek_next(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
116
+ {
117
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_next_impl, index, crs));
118
+ }
119
+ oinky_error oinky_index_cursor_seek_prev_impl(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
120
+ {
121
+ make_handle(crs).seek_prev();
122
+ return nky_success;
123
+ }
124
+ oinky_error oinky_index_cursor_seek_prev(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
125
+ {
126
+ return wrap_exception(boost::bind(oinky_index_cursor_seek_prev_impl, index, crs));
127
+ }
128
+
129
+ // Returns -1 if cursor is before beginning of sequence,
130
+ // 0 if it is on a valid element,
131
+ // and 1 if it is beyond the end of the sequence.
132
+ int8_t oinky_index_cursor_state(oinky_index_handle_t *index, oinky_index_row_cursor_t *crs)
133
+ {
134
+ db_t::index_cursor_handle c(make_handle(crs));
135
+ if (c.valid_data()) return 0;
136
+ if (c.before_begin()) return -1;
137
+ return 1;
138
+ }
139
+
140
+ static oinky_error oinky_index_cursor_get_values_impl(oinky_index_row_cursor_t *crs, oinky_column_selector_t *selector, size_t vcount, oinky_db_variant *out_values, size_t *out_value_count)
141
+ {
142
+ variant_cv_t *vals = (variant_cv_t *)alloca(sizeof(variant_cv_t) * selector->cs.column_count());
143
+ make_handle(crs).select(selector->cs).copy_to(vals, selector->cs.column_count());
144
+ if (selector->cs.column_count() < vcount) {
145
+ vcount = selector->cs.column_count();
146
+ }
147
+ for (size_t i=0;i<vcount;++i) {
148
+ out_values[i] = nky_variant_from_variant(vals[i]);
149
+ }
150
+ *out_value_count = vcount;
151
+ return nky_success;
152
+ }
153
+
154
+ oinky_error oinky_index_cursor_get_values(oinky_index_row_cursor_t *crs, oinky_column_selector_t *selector, size_t vcount, oinky_db_variant *out_values, size_t *out_value_count)
155
+ {
156
+ return wrap_exception(boost::bind(oinky_index_cursor_get_values_impl, crs, selector, vcount, out_values, out_value_count));
157
+ }
158
+
@@ -0,0 +1,224 @@
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
+ #include "nky_lib_core.hpp"
7
+
8
+
9
+ static oinky_error oinky_table_add_column_impl(oinky_table_handle_t *table, oinky_column_def defn)
10
+ {
11
+ table_column_def tcd(
12
+ nky_string_xform(defn.column_name),
13
+ nky_column_type_xform(defn.column_type),
14
+ nky_variant_xform(defn.default_value));
15
+
16
+ make_handle(table).columns().create(tcd);
17
+ return nky_success;
18
+ }
19
+ oinky_error oinky_table_add_column(oinky_table_handle_t *table, oinky_column_def defn)
20
+ {
21
+ return wrap_exception(boost::bind(oinky_table_add_column_impl, table, defn));
22
+ }
23
+
24
+ static oinky_error oinky_table_drop_column_impl(oinky_table_handle_t *table, db_string colname)
25
+ {
26
+ db_t::columns_accessor_t cols(make_handle(table).columns());
27
+ db_t::column_itr i = cols.find(colname);
28
+ if (i == cols.end()) {
29
+ throw_error(object_not_found());
30
+ }
31
+ cols.drop(i);
32
+ return nky_success;
33
+ }
34
+ oinky_error oinky_table_drop_column(oinky_table_handle_t *table, oinky_db_string colname)
35
+ {
36
+ return wrap_exception(boost::bind(oinky_table_drop_column_impl, table, nky_string_xform(colname)));
37
+ }
38
+
39
+ static oinky_error oinky_table_drop_index_impl(oinky_table_handle_t *table, db_string idxname)
40
+ {
41
+ db_t::indices_accessor_t ixs(make_handle(table).indices());
42
+ db_t::index_itr i = ixs.find(idxname);
43
+ if (i == ixs.end()) {
44
+ throw_error(object_not_found());
45
+ }
46
+ ixs.drop(i);
47
+ return nky_success;
48
+ }
49
+ oinky_error oinky_table_drop_index(oinky_table_handle_t *table, oinky_db_string idxname)
50
+ {
51
+ return wrap_exception(boost::bind(oinky_table_drop_index_impl, table, nky_string_xform(idxname)));
52
+ }
53
+
54
+
55
+ // Create a cursor at the beginning of the table.
56
+ static oinky_error oinky_table_cursor_new_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t **crs)
57
+ {
58
+ db_t::table_handle ih(make_handle(table));
59
+ db_t::table_cursor_handle cnew(ih.new_cursor(ih.rows_begin()));
60
+ *crs = (oinky_table_row_cursor_t *) cnew.raw();
61
+ return nky_success;
62
+ }
63
+ oinky_error oinky_table_cursor_new(oinky_table_handle_t *table, oinky_table_row_cursor_t **crs)
64
+ {
65
+ return wrap_exception(boost::bind(oinky_table_cursor_new_impl, table, crs));
66
+ }
67
+
68
+ static oinky_error oinky_table_cursor_clone_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *src, oinky_table_row_cursor_t **newcrs)
69
+ {
70
+ db_t::table_cursor_handle cs(make_handle(src));
71
+ db_t::table_cursor_handle cnew(cs.clone());
72
+ *newcrs = (oinky_table_row_cursor_t *) cnew.raw();
73
+ return nky_success;
74
+ }
75
+ oinky_error oinky_table_cursor_clone(oinky_table_handle_t *table, oinky_table_row_cursor_t *src, oinky_table_row_cursor_t **newcrs)
76
+ {
77
+ return wrap_exception(boost::bind(oinky_table_cursor_clone_impl, table, src, newcrs));
78
+ }
79
+
80
+ void oinky_table_cursor_free(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
81
+ {
82
+ // This deletes the internal cursor object.
83
+ // ***IMPORTANT***
84
+ // 1) It can only be called once.
85
+ // 2) It must be called before the DB is destroyed or not at all. Cursor
86
+ // lifetimes are bounded by DB lifetimes by definition, so DB destroy
87
+ // implicitly frees all cursors.
88
+ db_t::table_cursor_handle c(make_handle(crs));
89
+ make_handle(table).delete_cursor(c);
90
+ }
91
+
92
+ static oinky_error oinky_table_cursor_seek_last_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
93
+ {
94
+ db_t::table_handle ih(make_handle(table));
95
+ db_t::table_cursor_handle c(make_handle(crs));
96
+ ih.move_cursor(c, ih.rows_end());
97
+ c.seek_prev();
98
+ if (c.before_begin()) {
99
+ c.seek_next();
100
+ }
101
+ return nky_success;
102
+ }
103
+ oinky_error oinky_table_cursor_seek_last(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
104
+ {
105
+ return wrap_exception(boost::bind(oinky_table_cursor_seek_last_impl, table, crs));
106
+ }
107
+
108
+ static oinky_error oinky_table_cursor_seek_first_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
109
+ {
110
+ db_t::table_handle ih(make_handle(table));
111
+ db_t::table_cursor_handle c(make_handle(crs));
112
+ ih.move_cursor(c, ih.rows_begin());
113
+ return nky_success;
114
+ }
115
+ oinky_error oinky_table_cursor_seek_first(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
116
+ {
117
+ return wrap_exception(boost::bind(oinky_table_cursor_seek_first_impl, table, crs));
118
+ }
119
+
120
+ // increment/decrement
121
+ oinky_error oinky_table_cursor_seek_next_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
122
+ {
123
+ make_handle(crs).seek_next();
124
+ return nky_success;
125
+ }
126
+ oinky_error oinky_table_cursor_seek_next(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
127
+ {
128
+ return wrap_exception(boost::bind(oinky_table_cursor_seek_next_impl, table, crs));
129
+ }
130
+ oinky_error oinky_table_cursor_seek_prev_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
131
+ {
132
+ make_handle(crs).seek_prev();
133
+ return nky_success;
134
+ }
135
+ oinky_error oinky_table_cursor_seek_prev(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
136
+ {
137
+ return wrap_exception(boost::bind(oinky_table_cursor_seek_prev_impl, table, crs));
138
+ }
139
+
140
+ // Returns -1 if cursor is before beginning of sequence,
141
+ // 0 if it is on a valid element,
142
+ // and 1 if it is beyond the end of the sequence.
143
+ int8_t oinky_table_cursor_state(oinky_table_handle_t *table, oinky_table_row_cursor_t *crs)
144
+ {
145
+ db_t::table_cursor_handle c(make_handle(crs));
146
+ if (c.valid_data()) return 0;
147
+ if (c.before_begin()) return -1;
148
+ return 1;
149
+ }
150
+
151
+ static oinky_error oinky_table_cursor_get_values_impl(oinky_table_row_cursor_t *crs, oinky_column_selector_t *selector, size_t vcount, oinky_db_variant *out_values, size_t *out_value_count)
152
+ {
153
+ db_t::table_cursor_handle c(make_handle(crs));
154
+ variant_cv_t *vals = (variant_cv_t *)alloca(sizeof(variant_cv_t) * selector->cs.column_count());
155
+ c.select(selector->cs).copy_to(vals, selector->cs.column_count());
156
+ if (selector->cs.column_count() < vcount) {
157
+ vcount = selector->cs.column_count();
158
+ }
159
+ for (size_t i=0;i<vcount;++i) {
160
+ out_values[i] = nky_variant_from_variant(vals[i]);
161
+ }
162
+ *out_value_count = vcount;
163
+ return nky_success;
164
+ }
165
+
166
+ oinky_error oinky_table_cursor_get_values(oinky_table_row_cursor_t *crs, oinky_column_selector_t *selector, size_t vcount, oinky_db_variant *out_values, size_t *out_value_count)
167
+ {
168
+ return wrap_exception(boost::bind(oinky_table_cursor_get_values_impl, crs, selector, vcount, out_values, out_value_count));
169
+ }
170
+
171
+ // Update the columns (identified by column_selector) at the row (identified by cursor) with the given values.
172
+ static oinky_error oinky_table_update_row_at_index_cursor_impl(oinky_table_handle_t *table, oinky_index_row_cursor_t *cursor, oinky_column_selector_t *selector, size_t col_count, const variant_cv_t *values)
173
+ {
174
+ make_handle(table).update_row(make_handle(cursor), selector->cs, values, values + col_count);
175
+ return nky_success;
176
+ }
177
+
178
+ oinky_error oinky_table_update_row_at_index_cursor(oinky_table_handle_t *table, oinky_index_row_cursor_t *cursor, oinky_column_selector_t *selector, size_t col_count, const oinky_db_variant *values)
179
+ {
180
+ return wrap_unpack_variant_list(boost::bind(oinky_table_update_row_at_index_cursor_impl, table, cursor, selector, col_count, _1), col_count, values);
181
+ }
182
+
183
+ // Update the columns (identified by column_selector) at the row (identified by cursor) with the given values.
184
+ static oinky_error oinky_table_update_row_at_table_cursor_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *cursor, oinky_column_selector_t *selector, size_t col_count, const variant_cv_t *values)
185
+ {
186
+ make_handle(table).update_row(make_handle(cursor), selector->cs, values, values + col_count);
187
+ return nky_success;
188
+ }
189
+
190
+ oinky_error oinky_table_update_row_at_table_cursor(oinky_table_handle_t *table, oinky_table_row_cursor_t *cursor, oinky_column_selector_t *selector, size_t col_count, const oinky_db_variant *values)
191
+ {
192
+ return wrap_unpack_variant_list(boost::bind(oinky_table_update_row_at_table_cursor_impl, table, cursor, selector, col_count, _1), col_count, values);
193
+ }
194
+
195
+ static oinky_error oinky_table_delete_row_at_index_cursor_impl(oinky_table_handle_t *table, oinky_index_row_cursor_t *cursor)
196
+ {
197
+ make_handle(table).erase(make_handle(cursor));
198
+ return nky_success;
199
+ }
200
+ oinky_error oinky_table_delete_row_at_index_cursor(oinky_table_handle_t *table, oinky_index_row_cursor_t *cursor)
201
+ {
202
+ return wrap_exception(boost::bind(oinky_table_delete_row_at_index_cursor_impl, table, cursor));
203
+ }
204
+ static oinky_error oinky_table_delete_row_at_table_cursor_impl(oinky_table_handle_t *table, oinky_table_row_cursor_t *cursor)
205
+ {
206
+ make_handle(table).erase(make_handle(cursor));
207
+ return nky_success;
208
+ }
209
+ oinky_error oinky_table_delete_row_at_table_cursor(oinky_table_handle_t *table, oinky_table_row_cursor_t *cursor)
210
+ {
211
+ return wrap_exception(boost::bind(oinky_table_delete_row_at_table_cursor_impl, table, cursor));
212
+ }
213
+
214
+ static oinky_error oinky_table_insert_row_impl(oinky_table_handle_t *table, oinky_column_selector_t *selector, size_t col_count, const variant_cv_t *values)
215
+ {
216
+ make_handle(table).insert_row(selector->cs, values, values + col_count);
217
+ return nky_success;
218
+ }
219
+
220
+ oinky_error oinky_table_insert_row(oinky_table_handle_t *table, oinky_column_selector_t *selector, size_t col_count, const oinky_db_variant *values)
221
+ {
222
+ return wrap_unpack_variant_list(boost::bind(oinky_table_insert_row_impl, table, selector, col_count, _1), col_count, values);
223
+ }
224
+
@@ -0,0 +1,1284 @@
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 'date'
7
+ require 'ffi'
8
+
9
+ require 'oinky/error'
10
+ require 'oinky/query'
11
+ require 'oinky/nbuffer'
12
+ require 'oinky/version'
13
+ require 'oinky/modelbase'
14
+ require 'oinky/compiler'
15
+
16
+ module Oinky
17
+
18
+ class ExHash < Hash
19
+ def [](x)
20
+ # This allows access by string or symbol
21
+ return super(x.to_s)
22
+ end
23
+ end
24
+
25
+ # All of the C library interface is hidden within this internal module.
26
+ # The clean object-oriented lifetime-managed interface is built above this.
27
+ module Internal
28
+ extend FFI::Library
29
+
30
+ libdir = File.expand_path(__FILE__).sub(/\/[^\/]+$/,'')
31
+ # No way of knowing the extension reliably. (I've tried all the suggested
32
+ # methods and not one seems to actually work.)
33
+ fn = Dir.glob(File.join(libdir, 'liboinky_c*'))[0]
34
+ ffi_lib fn
35
+
36
+ typedef :int, :oinky_error
37
+
38
+ attach_function :convert_oinky_error, [:int], :string
39
+ attach_function :oinky_db_new, [], :pointer
40
+ attach_function :oinky_db_mount, [:pointer, :size_t, :pointer], :oinky_error
41
+ attach_function :oinky_db_free, [:pointer], :void
42
+
43
+ attach_function :oinky_db_is_modified_since, [:pointer, :uint64], :int
44
+ attach_function :oinky_db_create_savepoint, [:pointer, :pointer], :oinky_error
45
+ attach_function :oinky_db_savepoint_rollback, [:pointer, :uint64], :oinky_error
46
+
47
+ attach_function :oinky_db_prepare_pack, [:pointer, :pointer], :oinky_error
48
+ attach_function :oinky_db_complete_pack, [:pointer, :size_t, :pointer], :oinky_error
49
+
50
+ attach_function :oinky_debug_break, [], :void
51
+
52
+ def self.prepare_pack(db)
53
+ res = FFI::MemoryPointer.new :size_t
54
+ wrap_oinky(oinky_db_prepare_pack(db, res))
55
+ res.read_int
56
+ end
57
+
58
+ def self.wrap_oinky(err)
59
+ if err < 0
60
+ raise OinkyException.new(err, convert_oinky_error(err))
61
+ end
62
+ nil
63
+ end
64
+
65
+ class DB_string < FFI::Struct
66
+ include NativeBuffer
67
+
68
+ layout(:length, :size_t,
69
+ :bytes, :pointer)
70
+
71
+ attr_accessor :refs
72
+
73
+ def self.make(val)
74
+ nv = DB_string.new
75
+ nv.refs = make_in_place(nv, val)
76
+ nv
77
+ end
78
+
79
+ # Have to separate like this because FFI Doesn't allow
80
+ # assigning to structs by value.
81
+ def self.make_in_place(nv, val)
82
+ if val.is_a? String
83
+ val = NativeMallocBuffer.new(val)
84
+ nv[:bytes] = val.ptr
85
+ nv[:length] = val.length
86
+ elsif val.is_a? NativeBuffer
87
+ nv[:bytes] = val.ptr
88
+ nv[:length] = val.length
89
+ else
90
+ raise OinkyException.new("Argument is not a recognized string")
91
+ end
92
+
93
+ # Caller keeps the source object in memory as long as the variant.
94
+ # This is important whatever the source, ruby strings and
95
+ # NativeBuffers equally.
96
+ return val
97
+ end
98
+
99
+ # These are required by NativeBuffer
100
+ def ptr
101
+ self[:bytes]
102
+ end
103
+ def length
104
+ self[:length]
105
+ end
106
+ end
107
+ attach_function :oinky_table_name, [:pointer], DB_string.by_value
108
+ attach_function :oinky_index_name, [:pointer], DB_string.by_value
109
+
110
+ attach_function :oinky_table_count, [:pointer], :size_t
111
+ attach_function :oinky_tbl_column_count, [:pointer], :size_t
112
+ attach_function :oinky_idx_column_count, [:pointer], :size_t
113
+ attach_function :oinky_tbl_index_count, [:pointer], :size_t
114
+ attach_function :oinky_tbl_row_count, [:pointer], :size_t
115
+ attach_function :oinky_idx_row_count, [:pointer], :size_t
116
+
117
+ attach_function :oinky_db_get_table_handles, [:pointer, :pointer, :size_t], :oinky_error
118
+ attach_function :oinky_table_get_index_handles, [:pointer, :pointer, :size_t], :oinky_error
119
+
120
+ NkyTypeCodes = FFI::Enum.new([
121
+ :variant, 0,
122
+ :bit, 1,
123
+ :int8, 2,
124
+ :int16, 3,
125
+ :int32, 4,
126
+ :int64, 5,
127
+ :datetime, 6,
128
+ :string, 7,
129
+ :uint8, 8,
130
+ :uint16, 9,
131
+ :uint32, 10,
132
+ :uint64, 11,
133
+ :float32, 12,
134
+ :float64, 13
135
+ ])
136
+
137
+ class OinkyUnpackedDateTime < FFI::Struct
138
+ layout(:years_since_1900, :int32,
139
+ :months_since_january, :uint8,
140
+ :days_since_first_of_month, :uint8,
141
+ :hours_since_midnight, :uint8,
142
+ :minutes_since_hour, :uint8,
143
+ :seconds_since_minute, :uint8,
144
+ :microseconds_since_second, :uint32)
145
+ end
146
+
147
+ class OinkyDateTime < FFI::Struct
148
+ layout( :ptime, :int64 )
149
+
150
+ def self.parse(str)
151
+ # This can be a ruby string or a native string. This method
152
+ # doesn't take a reference.
153
+ #puts "parsing: " + str
154
+ dt = DateTime.parse(str.to_s)
155
+ return from_dt(dt)
156
+ end
157
+ def self.from_intval(intval)
158
+ v = OinkyDateTime.new
159
+ v[:ptime] = intval
160
+ v
161
+ end
162
+ def intval
163
+ self[:ptime]
164
+ end
165
+ def self.from_dt(dt)
166
+ dt = dt.new_offset(0)
167
+
168
+ unpacked = OinkyUnpackedDateTime.new
169
+ unpacked[:years_since_1900] = dt.year - 1900
170
+ unpacked[:months_since_january] = dt.month - 1
171
+ unpacked[:days_since_first_of_month] = dt.day - 1
172
+ unpacked[:hours_since_midnight] = dt.hour
173
+ unpacked[:minutes_since_hour] = dt.minute
174
+ unpacked[:seconds_since_minute] = dt.second
175
+ unpacked[:microseconds_since_second] = (dt.second_fraction * 1000000).to_i
176
+
177
+ v = OinkyDateTime.new
178
+ Internal.wrap_oinky(Internal.nky_pack_datetime(v, unpacked))
179
+ v
180
+ end
181
+ def rb_val
182
+ unpacked = OinkyUnpackedDateTime.new
183
+ Internal.wrap_oinky(Internal.nky_unpack_datetime(self, unpacked))
184
+ # Make a datetime, accounting for sloppy encoding with overlapping
185
+ # months and days
186
+ dt = DateTime.
187
+ new(1900 + unpacked[:years_since_1900]).
188
+ next_month(unpacked[:months_since_january]).
189
+ next_day(unpacked[:days_since_first_of_month])
190
+
191
+ daypart =
192
+ ((((((unpacked[:hours_since_midnight] * 60) +
193
+ unpacked[:minutes_since_hour]) * 60) +
194
+ unpacked[:seconds_since_minute]) * 1000000) +
195
+ unpacked[:microseconds_since_second]).quo(3600000000 * 24)
196
+
197
+ return dt + daypart
198
+ end
199
+ def to_datetime
200
+ rb_val
201
+ end
202
+ end
203
+
204
+ attach_function(:nky_unpack_datetime,
205
+ [OinkyDateTime.by_value, OinkyUnpackedDateTime], :oinky_error)
206
+ attach_function(:nky_pack_datetime,
207
+ [OinkyDateTime, OinkyUnpackedDateTime.by_value], :oinky_error)
208
+
209
+ class Variant < FFI::Struct
210
+ end
211
+
212
+ class VariantSet < FFI::MemoryPointer
213
+ attr_accessor :refs
214
+
215
+ def count
216
+ @refs.count
217
+ end
218
+
219
+ def initialize(count)
220
+ super(Variant, count)
221
+ end
222
+
223
+ def [](x)
224
+ Variant.new(super(x))
225
+ end
226
+ end
227
+
228
+ class Variant
229
+ class ValueT < FFI::Union
230
+ layout(:string_value, DB_string,
231
+ :int_value, :int64,
232
+ :uint_value, :uint64,
233
+ :f32_value, FFI::TYPE_FLOAT32,
234
+ :f64_value, FFI::TYPE_FLOAT64,
235
+ :dt_value, :int64)
236
+ end
237
+
238
+ layout(:value, ValueT,
239
+ :type, NkyTypeCodes)
240
+
241
+ # We must do this again. We can't rely on the one in the DB_string
242
+ # we create because that can get discarded as soon as we are finished
243
+ # using it to initialize.
244
+ attr_accessor :refs
245
+
246
+ def self.make(val)
247
+ nv = Variant.new
248
+ nv.refs = make_in_place(nv, val)
249
+ nv
250
+ end
251
+ def self.make_in_place(nv, val)
252
+ if val.is_a?(String) or val.is_a?(NativeBuffer)
253
+ nv[:type] = NkyTypeCodes[:string]
254
+ return DB_string.make_in_place(nv[:value][:string_value], val)
255
+ elsif val.is_a?(DateTime) or val.is_a?(Time) or val.is_a?(Date) or val.is_a?(OinkyDateTime)
256
+ dt = val.to_datetime
257
+ nv[:value][:dt_value] = OinkyDateTime.from_dt(dt).intval
258
+ nv[:type] = NkyTypeCodes[:datetime]
259
+ return nil
260
+ elsif val.is_a?(Fixnum) or val.is_a?(Bignum)
261
+ if val < 0
262
+ nv[:value][:int_value] = val
263
+ nv[:type] = NkyTypeCodes[:int64]
264
+ else
265
+ nv[:value][:uint_value] = val
266
+ nv[:type] = NkyTypeCodes[:uint64]
267
+ end
268
+ return nil
269
+ elsif val.is_a?(Float)
270
+ nv[:value][:f64_value] = val
271
+ nv[:type] = NkyTypeCodes[:float64]
272
+ return nil
273
+ elsif val.is_a?(TrueClass)
274
+ nv[:value][:int_value] = 1
275
+ nv[:type] = NkyTypeCodes[:bit]
276
+ return nil
277
+ elsif val.is_a?(FalseClass)
278
+ nv[:value][:int_value] = 0
279
+ nv[:type] = NkyTypeCodes[:bit]
280
+ return nil
281
+ else
282
+ raise OinkyException.new("Don't know how to make variant from given value of type: #{val.class}")
283
+ end
284
+ return nil
285
+ end
286
+
287
+ def self.make_from_set(set)
288
+ ptr = VariantSet.new(set.size)
289
+ ptr.refs = []
290
+ set.each_with_index{|v, i|
291
+ ptr.refs << make_in_place(ptr[i], v)
292
+ }
293
+ ptr
294
+ end
295
+
296
+ # Convert to a ruby value
297
+ def rb_val
298
+ v = self[:value]
299
+ case self[:type]
300
+ when :string
301
+ return v[:string_value].rb_str
302
+ when :datetime
303
+ return OinkyDateTime.from_intval(v[:dt_value]).to_datetime
304
+ when :bit
305
+ return v[:int_value] == 1
306
+
307
+ when :int8
308
+ return v[:int_value]
309
+ when :int16
310
+ return v[:int_value]
311
+ when :int32
312
+ return v[:int_value]
313
+ when :int64
314
+ return v[:int_value]
315
+
316
+ when :uint8
317
+ return v[:uint_value]
318
+ when :uint16
319
+ return v[:uint_value]
320
+ when :uint32
321
+ return v[:uint_value]
322
+ when :uint64
323
+ return v[:uint_value]
324
+
325
+ when :float32
326
+ return v[:f32_value]
327
+ when :float64
328
+ return v[:f64_value]
329
+ end
330
+ raise OinkyException.new("Invalid type code in Variant.")
331
+ end
332
+ end
333
+
334
+ class ColumnDefn < FFI::Struct
335
+ layout( :column_name, DB_string,
336
+ :column_type, NkyTypeCodes,
337
+ :default_value, Variant )
338
+
339
+ attr_accessor :refs
340
+
341
+ def self.make(name, type, value)
342
+ v = ColumnDefn.new
343
+ cn = DB_string.make_in_place(v[:column_name], name)
344
+ dv = Variant.make_in_place(v[:default_value], value)
345
+ v[:column_type] = NkyTypeCodes[type]
346
+
347
+ # Keep these objects in memory
348
+ v.refs = [cn, dv]
349
+ v
350
+ end
351
+
352
+ class ColumnSet < FFI::MemoryPointer
353
+ attr_accessor :refs
354
+
355
+ def count
356
+ @refs.count
357
+ end
358
+
359
+ def initialize(count)
360
+ super(ColumnDefn, count)
361
+ end
362
+
363
+ def [](x)
364
+ ColumnDefn.new(super(x))
365
+ end
366
+ end
367
+ end
368
+
369
+ def self.make_hash(src, keys)
370
+ h = ExHash.new
371
+ keys.each {|k| h[k] = src[k] }
372
+ h
373
+ end
374
+
375
+ class IndexColumnDefn < FFI::Struct
376
+ layout( :column_name, DB_string,
377
+ :sort_ascending, :int8 )
378
+
379
+ attr_accessor :refs
380
+
381
+ def to_h
382
+ { :column_name=>self[:column_name].to_s,
383
+ :sort_ascending=>(self[:sort_ascending] != 0) }
384
+ end
385
+
386
+ def self.make(name, ascending = true)
387
+ v = IndexColumnDefn.new
388
+ v.refs = make_in_place(v, name, ascending)
389
+ v
390
+ end
391
+ def self.make_in_place(nv, name, ascending = true)
392
+ cn = DB_string.make_in_place(nv[:column_name], name)
393
+ nv[:sort_ascending] = ascending ? 1 : 0
394
+ cn
395
+ end
396
+
397
+ class IxColumnSet < FFI::MemoryPointer
398
+ attr_accessor :refs
399
+
400
+ def count
401
+ @refs.count
402
+ end
403
+
404
+ def initialize(count)
405
+ super(IndexColumnDefn, count)
406
+ end
407
+
408
+ def [](x)
409
+ IndexColumnDefn.new(super(x))
410
+ end
411
+ end
412
+
413
+ def self.make_from_set(set)
414
+ values = []
415
+ set.each {|col|
416
+ if col.is_a?(Symbol) or col.is_a?(String)
417
+ values << {:column_name=>col.to_s, :sort_ascending=>true}
418
+ elsif col.is_a? Hash
419
+ name = (col[:column_name] || col[:name] || col[:column]).to_s
420
+ ascending = true
421
+ [:ascending, :sort_ascending].each {|k|
422
+ ascending = false if (col.has_key?(k) and not col[k])
423
+ }
424
+ [:descending, :sort_descending].each {|k|
425
+ ascending = false if col[k]
426
+ }
427
+ values << {:column_name=>name, :sort_ascending=>ascending}
428
+ else
429
+ raise OinkyException.new("Unrecognized index columnset entry: #{col.class}")
430
+ end
431
+ }
432
+ ptr = IxColumnSet.new(values.size)
433
+ ptr.refs = []
434
+ values.each_with_index{|v, i|
435
+ ptr.refs << make_in_place(ptr[i], v[:column_name], v[:sort_ascending])
436
+ }
437
+ ptr
438
+ end
439
+ end
440
+
441
+ #oinky_error oinky_db_create_table(oinky_db_handle_t *db, const char *name, oinky_table_handle_t **handle);
442
+ attach_function(:oinky_db_create_table,
443
+ [:pointer, :string, :pointer], :oinky_error)
444
+ attach_function(:oinky_db_drop_table,
445
+ [:pointer, DB_string.by_value], :oinky_error)
446
+ attach_function(:oinky_table_add_column,
447
+ [:pointer, ColumnDefn.by_value], :oinky_error)
448
+ attach_function(:oinky_table_get_column_defs,
449
+ [:pointer, :pointer, :size_t], :size_t)
450
+ attach_function(:oinky_index_get_definition,
451
+ [:pointer, :pointer, :pointer, :pointer, :size_t], :oinky_error)
452
+ attach_function(:oinky_table_drop_column,
453
+ [:pointer, DB_string.by_value], :oinky_error)
454
+
455
+ attach_function(:oinky_table_add_index,
456
+ [:pointer, :string, :int8, :size_t, :pointer, :pointer],
457
+ :oinky_error)
458
+ attach_function(:oinky_table_drop_index,
459
+ [:pointer, DB_string.by_value], :oinky_error)
460
+
461
+ attach_function(:oinky_table_make_column_selector,
462
+ [:pointer, :size_t, :pointer, :pointer], :oinky_error)
463
+ attach_function(:oinky_table_delete_column_selector,
464
+ [:pointer, :pointer], :oinky_error)
465
+
466
+ # Index cursors
467
+ attach_function(:oinky_index_cursor_new,
468
+ [:pointer, :pointer], :oinky_error)
469
+ attach_function(:oinky_index_cursor_free,
470
+ [:pointer, :pointer], :void)
471
+ attach_function(:oinky_index_cursor_clone,
472
+ [:pointer, :pointer, :pointer], :oinky_error)
473
+ attach_function(:oinky_index_cursor_seek_first,
474
+ [:pointer, :pointer], :oinky_error)
475
+ attach_function(:oinky_index_cursor_seek_lower_bound,
476
+ [:pointer, :pointer, :size_t, :pointer], :oinky_error)
477
+ attach_function(:oinky_index_cursor_seek_last,
478
+ [:pointer, :pointer], :oinky_error)
479
+ attach_function(:oinky_index_cursor_seek_prev,
480
+ [:pointer, :pointer], :oinky_error)
481
+ attach_function(:oinky_index_cursor_seek_next,
482
+ [:pointer, :pointer], :oinky_error)
483
+ attach_function(:oinky_index_cursor_state,
484
+ [:pointer, :pointer], :int8)
485
+ attach_function(:oinky_index_cursor_get_values,
486
+ [:pointer, :pointer, :size_t, :pointer, :pointer], :oinky_error)
487
+
488
+ # Table cursors
489
+ attach_function(:oinky_table_cursor_new,
490
+ [:pointer, :pointer], :oinky_error)
491
+ attach_function(:oinky_table_cursor_free,
492
+ [:pointer, :pointer], :void)
493
+ attach_function(:oinky_table_cursor_clone,
494
+ [:pointer, :pointer, :pointer], :oinky_error)
495
+ attach_function(:oinky_table_cursor_seek_first,
496
+ [:pointer, :pointer], :oinky_error)
497
+ attach_function(:oinky_table_cursor_seek_last,
498
+ [:pointer, :pointer], :oinky_error)
499
+ attach_function(:oinky_table_cursor_seek_prev,
500
+ [:pointer, :pointer], :oinky_error)
501
+ attach_function(:oinky_table_cursor_seek_next,
502
+ [:pointer, :pointer], :oinky_error)
503
+ attach_function(:oinky_table_cursor_state,
504
+ [:pointer, :pointer], :int8)
505
+ attach_function(:oinky_table_cursor_get_values,
506
+ [:pointer, :pointer, :size_t, :pointer, :pointer], :oinky_error)
507
+
508
+ # Data insertion
509
+ attach_function(:oinky_table_update_row_at_table_cursor,
510
+ [:pointer, :pointer, :pointer, :size_t, :pointer], :oinky_error)
511
+ attach_function(:oinky_table_update_row_at_index_cursor,
512
+ [:pointer, :pointer, :pointer, :size_t, :pointer], :oinky_error)
513
+ attach_function(:oinky_table_delete_row_at_table_cursor,
514
+ [:pointer, :pointer], :oinky_error)
515
+ attach_function(:oinky_table_delete_row_at_index_cursor,
516
+ [:pointer, :pointer], :oinky_error)
517
+ attach_function(:oinky_table_insert_row,
518
+ [:pointer, :pointer, :size_t, :pointer], :oinky_error)
519
+
520
+ # The cursor types and implementation methods are different for index cursors
521
+ # and table cursors. However, they operate almost identically. This is the
522
+ # common implementation. Index and Table specialize only slightly.
523
+ class CursorBase
524
+ def initialize(host)
525
+ if host.is_a? host_class
526
+ @host = host
527
+ @hosthandle = @host.send(:eval, "@handle")
528
+ c = FFI::MemoryPointer.new(:pointer)
529
+ Internal.wrap_oinky(impl(:cursor_new).call(@hosthandle, c))
530
+ @handle = c.read_pointer
531
+ seek_first
532
+ elsif host.is_a? self.class
533
+ c = FFI::MemoryPointer.new(:pointer)
534
+ h, @hosthandle, @host = host.send(:eval, "[@handle, @hosthandle, @host]")
535
+ Internal.wrap_oinky(impl(:cursor_clone).call(@hosthandle, h, c))
536
+ @handle = c.read_pointer
537
+ else
538
+ raise OinkyException.new("Unknown argument type to #{self.class}.new")
539
+ end
540
+
541
+ # Cursors are automatically destroyed when the DB is destroyed.
542
+ # If we call cursor deletion after DB deletion, that is equivalent to
543
+ # freeing twice. It's bad. But we have no way of controlling the
544
+ # order of finalizer invocation (between cursor and DB) short of
545
+ # referencing the DB in the cursor finalizer. But that causes the DB
546
+ # to stick around for a whole extra GC cycle. Generally, that overhead
547
+ # is much worse than leaking the cursor objects for the lifetime of
548
+ # the DB. So, we have two options:
549
+ # 1) Keep a reference on the DB object in the cursor finalizer, causing
550
+ # a full GC-cycle delay in freeing DB resources.
551
+ # 2) Leak cursor objects always, letting them be cleaned up implicitly
552
+ # when the DB is destroyed.
553
+ # This is a configuration property called Oinky.implicit_cursor_cleanup
554
+ # which defaults to 'true', indicating that we pursue strategy 2. It
555
+ # is a property of the database object instance which applies to all
556
+ # cursors derived from that instance (table or index).
557
+ # cursors
558
+ # applies to all cursors created after the property is set.
559
+ # Changing the global or db-local value dynamically at any time is
560
+ # safe. It should generally only be set to 'false' for instances that
561
+ # will be long-lived, where the aggregate overhead of cursors created
562
+ # (tens of bytes) might compare to the overhead of the database itself.
563
+
564
+ unless @host.db.implicit_cursor_cleanup
565
+ ObjectSpace.define_finalizer( self, self.class.finalize(impl(:cursor_free), @handle, {:db=>@host.db}) )
566
+ end
567
+ end
568
+
569
+ def self.finalize(m, ptr, refs)
570
+ proc { m.call(ptr); refs[:db] = nil }
571
+ end
572
+
573
+ def seek_first
574
+ Internal.wrap_oinky(impl(:cursor_seek_first).call(@hosthandle, @handle))
575
+ self
576
+ end
577
+ def seek_last
578
+ Internal.wrap_oinky(impl(:cursor_seek_last).call(@hosthandle, @handle))
579
+ self
580
+ end
581
+ def seek_next
582
+ Internal.wrap_oinky(impl(:cursor_seek_next).call(@hosthandle, @handle))
583
+ self
584
+ end
585
+ def seek_prev
586
+ Internal.wrap_oinky(impl(:cursor_seek_prev).call(@hosthandle, @handle))
587
+ self
588
+ end
589
+ def state_val
590
+ impl(:cursor_state).call(@hosthandle, @handle)
591
+ end
592
+ def is_valid?
593
+ state_val == 0
594
+ end
595
+ def at_end?
596
+ state_val == 1
597
+ end
598
+ def before_begin?
599
+ state_val == -1
600
+ end
601
+ def clone
602
+ self.class.new(self)
603
+ end
604
+ def column_selector_default(cs = nil)
605
+ cs = self.table.select_all_columns unless cs
606
+ cs = self.table.select_columns(cs) unless cs.is_a? ColumnSelector
607
+ cs
608
+ end
609
+ def select(cs = nil)
610
+ cs = self.column_selector_default(cs)
611
+ vals = self.select_values(cs)
612
+ r = ExHash.new
613
+ cns = cs.cols
614
+ vals.each_with_index{|v,i|
615
+ r[cns[i]] = v
616
+ }
617
+ return r
618
+ end
619
+ def select_all
620
+ self.select(nil)
621
+ end
622
+ # Like <select>, but just returns values as an array, rather
623
+ # than as a hash
624
+ def select_values(cs = nil)
625
+ cs = column_selector_default(cs)
626
+ vs = Internal::VariantSet.new(cs.size)
627
+ out_count = FFI::MemoryPointer.new :size_t
628
+ Internal.wrap_oinky(impl(:cursor_get_values).call(@handle, cs.handle, cs.size, vs, out_count))
629
+ (0..out_count.read_int-1).map{|i| vs[i].rb_val }
630
+ end
631
+ def select_all_values
632
+ self.select_values(nil)
633
+ end
634
+ # Move this cursor forward until the end (or until enumeration is stopped)
635
+ def each_forward(cs = nil)
636
+ e = ValuesEnumerator.new {|blk|
637
+ # The cursor may not begin in a valid position
638
+ self.seek_next unless self.is_valid?
639
+ while self.is_valid?
640
+ blk.yield self.select(cs)
641
+ self.seek_next
642
+ end
643
+ }
644
+ if block_given?
645
+ e.each{|k| yield k}
646
+ else
647
+ return e
648
+ end
649
+ self
650
+ end
651
+ def each_backward(cs = nil)
652
+ e = ValuesEnumerator.new {|blk|
653
+ # The cursor may not begin in a valid position
654
+ self.seek_prev unless self.is_valid?
655
+ while self.is_valid?
656
+ blk.yield self.select(cs)
657
+ self.seek_prev
658
+ end
659
+ }
660
+ if block_given?
661
+ e.each{|k| yield k}
662
+ else
663
+ return e
664
+ end
665
+ self
666
+ end
667
+ end # class CursorBase
668
+ end # Internal
669
+
670
+ # These should be exposed for defining column values, even though we do not
671
+ # expose the Variant concept itself.
672
+ column_types = Internal::NkyTypeCodes.to_h.keys.sort
673
+ ValueTypes = column_types.select{|x| x != :variant}
674
+ ColumnTypes ||= column_types
675
+
676
+ # ColumnTypes may have been previously defined (by the compiler)
677
+ # but if so, the definition should have been identical.
678
+ unless ColumnTypes == column_types
679
+ raise OinkyException.new("Mismatched column types definition: " +
680
+ "#{ColumnTypes.inspect} != #{column_types.inspect}")
681
+ end
682
+
683
+ class ColumnSelector
684
+ def initialize(table, colnames)
685
+ th = table.send(:eval, "@handle")
686
+
687
+ # Make an array of strings.
688
+ colnames = [colnames] unless colnames.is_a? Enumerable
689
+ colnames = colnames.to_a
690
+ colnames = colnames.map {|v| v.to_s}
691
+
692
+ # Make an array of c-strings
693
+ cols = FFI::MemoryPointer.new(:string, colnames.size)
694
+ native = NativeMallocBuffer.new((colnames * "\x00") + "\x00")
695
+ ptr = native.ptr
696
+ colnames.each_with_index{|s,i|
697
+ # The routine expects null-terminated strings.
698
+ cols[i].write_pointer(ptr)
699
+ ptr = ptr + s.length + 1
700
+ }
701
+
702
+ cs = FFI::MemoryPointer.new :pointer
703
+ Internal.wrap_oinky(Internal.oinky_table_make_column_selector(th, colnames.size, cols, cs))
704
+ @handle, @cols = [cs.read_pointer, colnames]
705
+
706
+ ObjectSpace.define_finalizer( self, self.class.finalize(table, @handle) )
707
+ end
708
+
709
+ def self.finalize(tptr, hptr)
710
+ proc { Internal.oinky_table_delete_column_selector(tptr, hptr) }
711
+ end
712
+
713
+ # column selectors are immutable. cloning would cause us to free twice
714
+ # anyway.
715
+ def clone
716
+ self
717
+ end
718
+
719
+ attr_reader :handle, :cols
720
+
721
+ def size
722
+ cols.size
723
+ end
724
+ end
725
+
726
+ # This is a column selector that's not bound to a particular table
727
+ class OpenSelector
728
+ attr_accessor :cols
729
+ def initialize(cols)
730
+ @cols = cols
731
+ end
732
+ def from(target)
733
+ target.each(@cols)
734
+ end
735
+ end
736
+ def self.select(cols)
737
+ OpenSelector.new(cols)
738
+ end
739
+ def self.select_all
740
+ select(nil)
741
+ end
742
+
743
+ module RowSet
744
+ def cursor_each
745
+ e = Enumerator.new {|blk|
746
+ c = self.new_cursor
747
+ while c.is_valid?
748
+ blk.yield c
749
+ c.seek_next
750
+ end
751
+ }
752
+ if block_given?
753
+ e.each{|k| yield k}
754
+ else
755
+ return e
756
+ end
757
+ end
758
+
759
+ def each(cs = nil)
760
+ # Making the selector first will be most efficient
761
+ cs = @table.select_columns(cs)
762
+
763
+ e = ValuesEnumerator.new {|blk|
764
+ self.cursor_each { |c| blk.yield c.select(cs) }
765
+ }
766
+ if block_given?
767
+ e.each{|k| yield k}
768
+ else
769
+ return e
770
+ end
771
+ end
772
+
773
+ def new_cursor(*a)
774
+ self.class::Cursor.new(self, *a)
775
+ end
776
+ end # module RowSet
777
+
778
+ class Table
779
+ include RowSet
780
+
781
+ def initialize(db, handle)
782
+ @db, @handle = db, handle
783
+ @table = self
784
+ end
785
+
786
+ attr_reader :db
787
+
788
+ def column_count
789
+ Internal.oinky_tbl_column_count(@handle)
790
+ end
791
+ def index_count
792
+ Internal.oinky_tbl_index_count(@handle)
793
+ end
794
+ def row_count
795
+ Internal.oinky_tbl_row_count(@handle)
796
+ end
797
+
798
+ def add_column(*a)
799
+ add_columns([a])
800
+ end
801
+ def add_columns(cset)
802
+ @db.atomic {
803
+ if cset.is_a? Hash
804
+ cset.each {|name,v|
805
+ add_column_internal(v.merge(:name=>name))
806
+ }
807
+ else
808
+ cset.each {|a|
809
+ add_column_internal(a)
810
+ }
811
+ end
812
+ # Refresh the default column selector
813
+ reselect_all_columns
814
+ }
815
+ self
816
+ end
817
+
818
+ private
819
+ def add_column_internal(a)
820
+ a = a[0] if (a.size == 1)
821
+ name, type, default_v = a
822
+ if a.is_a? Hash
823
+ h = a
824
+ name = (h[:column_name] || h[:name])
825
+ type = (h[:column_type] || h[:type])
826
+ default_v = (h[:default_value] || h[:default])
827
+ end
828
+ name = name.to_s if name.is_a? Symbol
829
+ # This will raise if the parameters are wrong.
830
+ defn = Internal::ColumnDefn.make(name, type, default_v)
831
+ # All parameters are good at this point
832
+ Internal.wrap_oinky(Internal.oinky_table_add_column(@handle, defn))
833
+ end
834
+
835
+ public
836
+ def add_index(opts)
837
+ name = opts[:name]
838
+ raise OinkyException.new("Invalid index name") unless name.length
839
+ colset = (opts[:columns] or opts[:columnset])
840
+ raise OinkyException.new("Oinky::Table::add_index: ':columns' not specified") unless colset
841
+ raise OinkyException.new("Oinky::Table::add_index: ':unique' not specified") unless opts.has_key?(:unique)
842
+ unique = opts[:unique]
843
+
844
+ iptr = FFI::MemoryPointer.new :pointer
845
+ colset = Internal::IndexColumnDefn.make_from_set(colset)
846
+ Internal.wrap_oinky(Internal.oinky_table_add_index(
847
+ @handle,
848
+ name,
849
+ unique ? 1 : 0,
850
+ colset.count,
851
+ colset,
852
+ iptr))
853
+ #return Index.new(iptr.read_pointer)
854
+ #
855
+ # It makes more sense that this should be like column than like
856
+ # create_table
857
+ self
858
+ end
859
+
860
+ def name
861
+ return @tblname if @tblname
862
+ dbs = Internal.oinky_table_name(@handle)
863
+ @tblname = dbs.to_s
864
+ end
865
+
866
+ def columns
867
+ sz = self.column_count()
868
+ defs = Internal::ColumnDefn::ColumnSet.new(sz)
869
+ rsz = Internal.oinky_table_get_column_defs(@handle, defs, sz)
870
+ unless rsz == sz
871
+ raise OinkyException.new("Oinky: Unknown interface error - columncount [#{rsz}, #{sz}]")
872
+ end
873
+ cols = ExHash.new
874
+ (0..sz-1).each{|i|
875
+ cd = defs[i]
876
+ cols[cd[:column_name].to_s] = {:type=>cd[:column_type], :default =>cd[:default_value].rb_val}
877
+ }
878
+ cols
879
+ end
880
+
881
+ def drop_column(colname)
882
+ dbs = Internal::DB_string.make(colname.to_s)
883
+ Internal.wrap_oinky(Internal.oinky_table_drop_column(@handle, dbs))
884
+ # Refresh the colset cache
885
+ reselect_all_columns
886
+ self
887
+ end
888
+
889
+ def select_columns(colnames)
890
+ return self.select_all_columns unless colnames
891
+ return colnames if colnames.is_a? ColumnSelector
892
+ ColumnSelector.new(self, colnames)
893
+ end
894
+
895
+ # Caches the selector
896
+ def select_all_columns
897
+ @selectall ||= self.reselect_all_columns
898
+ end
899
+ # Refreshes the selector cache
900
+ def reselect_all_columns
901
+ @selectall = self.select_columns(self.columns.keys)
902
+ end
903
+
904
+ def select(cs)
905
+ cs = self.select_columns(cs)
906
+ e = self.each(cs)
907
+ if block_given?
908
+ e.each {|k| yield k}
909
+ else
910
+ return e
911
+ end
912
+ end
913
+
914
+ def select_all
915
+ e = self.select(self.select_all_columns)
916
+ if block_given?
917
+ e.each {|k| yield k}
918
+ else
919
+ return e
920
+ end
921
+ end
922
+
923
+ def indices
924
+ sz = self.index_count()
925
+ handles = FFI::MemoryPointer.new(:pointer, sz)
926
+ rsz = Internal.oinky_table_get_index_handles(@handle, handles, sz)
927
+ unless rsz == sz
928
+ raise OinkyException.new("Oinky: Unknown interface error - indexcount")
929
+ end
930
+ r = ExHash.new
931
+ (0..sz-1).each{|i|
932
+ ix = Index.new(self, handles[i].read_pointer)
933
+ r[ix.name] = ix
934
+ }
935
+ return r
936
+ end
937
+
938
+ def drop_index(ixname)
939
+ dbs = Internal::DB_string.make(ixname.to_s)
940
+ Internal.wrap_oinky(Internal.oinky_table_drop_index(@handle, dbs))
941
+ self
942
+ end
943
+
944
+ class Cursor < Internal::CursorBase
945
+ private
946
+ def impl(name)
947
+ Internal.method("oinky_table_#{name}".to_sym)
948
+ end
949
+ def host_class
950
+ Table
951
+ end
952
+ public
953
+ def table
954
+ @host
955
+ end
956
+ # Cannot seek by value in a table
957
+ if method_defined? :seek
958
+ undef :seek
959
+ end
960
+ end # class Cursor
961
+
962
+ def update_row(where, cs, values = nil)
963
+ unless where.is_a? Internal::CursorBase
964
+ raise OinkyException.new("Invalid row specifier. Use a Cursor object.")
965
+ end
966
+ cs, count, vals = make_selector_and_values(:update_row, cs, values)
967
+ p = nil
968
+ if (where.is_a? Table::Cursor)
969
+ p = Internal.method :oinky_table_update_row_at_table_cursor
970
+ else
971
+ p = Internal.method :oinky_table_update_row_at_index_cursor
972
+ end
973
+ Internal.wrap_oinky(
974
+ p.call(
975
+ @handle,
976
+ where.send(:eval,"@handle"),
977
+ cs.handle,
978
+ count,
979
+ vals))
980
+ self
981
+ end #def update_row
982
+
983
+ def delete_row(where)
984
+ unless where.is_a? Internal::CursorBase
985
+ raise OinkyException.new("Invalid row specifier. Use a Cursor object.")
986
+ end
987
+ p = nil
988
+ if (where.is_a? Table::Cursor)
989
+ p = Internal.method :oinky_table_delete_row_at_table_cursor
990
+ else
991
+ p = Internal.method :oinky_table_delete_row_at_index_cursor
992
+ end
993
+ Internal.wrap_oinky(
994
+ p.call(
995
+ @handle,
996
+ where.send(:eval,"@handle")))
997
+ self
998
+ end #def delete_row
999
+
1000
+ def insert_row(cs, values = nil)
1001
+ cs, count, vals = make_selector_and_values(:insert_row, cs, values)
1002
+ Internal.wrap_oinky(
1003
+ Internal.oinky_table_insert_row(
1004
+ @handle,
1005
+ cs.handle,
1006
+ count,
1007
+ vals))
1008
+ self
1009
+ end #def insert_row
1010
+
1011
+ private
1012
+ def make_selector_and_values(mname, cs, values)
1013
+ if cs.is_a? Hash
1014
+ kg = []
1015
+ vg = []
1016
+ cs.each {|k,v|
1017
+ kg << k
1018
+ vg << v
1019
+ }
1020
+ cs = select_columns(kg)
1021
+ return make_selector_and_values(mname, cs, vg)
1022
+ else
1023
+ unless cs.is_a? ColumnSelector
1024
+ raise OinkyException.new("#{mname} requires a ColumnSelector and Value-array or a column-value Hash.")
1025
+ end
1026
+ unless cs.size == values.size
1027
+ raise OinkyException.new("Size of values array must match selector width.")
1028
+ end
1029
+ return [cs, cs.size, Internal::Variant.make_from_set(values)]
1030
+ end
1031
+ end # make_selector_and_values
1032
+ end # class Table
1033
+
1034
+ class Index
1035
+ include RowSet
1036
+
1037
+ def initialize(table, handle)
1038
+ @table, @handle = table, handle
1039
+ end
1040
+
1041
+ attr_reader :table
1042
+ def db ; @table.db ; end
1043
+
1044
+ def column_count
1045
+ Internal.oinky_idx_column_count(@handle)
1046
+ end
1047
+ def row_count
1048
+ Internal.oinky_idx_row_count(@handle)
1049
+ end
1050
+
1051
+ def definition
1052
+ return @definition if @definition
1053
+
1054
+ sz = self.column_count()
1055
+ nm = FFI::MemoryPointer.new(Internal::DB_string)
1056
+ uniq = FFI::MemoryPointer.new(:char)
1057
+ defs = Internal::IndexColumnDefn::IxColumnSet.new(sz)
1058
+ Internal.wrap_oinky(Internal.oinky_index_get_definition(@handle, nm, uniq, defs, sz))
1059
+
1060
+ # Unlike the table, ordering of the index column definitions is
1061
+ # semantically important.
1062
+ @definition = {
1063
+ :name=>Internal::DB_string.new(nm).to_s,
1064
+ :unique=>uniq.read_char != 0 ? true : false,
1065
+ :columns=> (0..sz-1).map{|i|
1066
+ defs[i].to_h
1067
+ }
1068
+ }
1069
+ @colnames = @definition[:columns].map{|c| c[:column_name] }
1070
+ return @definition
1071
+ end
1072
+
1073
+ def columns
1074
+ definition
1075
+ @colnames
1076
+ end
1077
+
1078
+ def selector
1079
+ table.select_columns(self.columns)
1080
+ end
1081
+
1082
+ def name
1083
+ return @idxname if @idxname
1084
+ dbs = Internal.oinky_index_name(@handle)
1085
+ @idxname = dbs.to_s
1086
+ end
1087
+
1088
+ class Cursor < Internal::CursorBase
1089
+ private
1090
+ def impl(name)
1091
+ Internal.method("oinky_index_#{name}".to_sym)
1092
+ end
1093
+ def host_class
1094
+ Index
1095
+ end
1096
+ public
1097
+ def table
1098
+ @host.table
1099
+ end
1100
+
1101
+ def initialize(host, where = nil)
1102
+ super(host)
1103
+ if where
1104
+ seek(where)
1105
+ end
1106
+ end
1107
+
1108
+ def seek(where)
1109
+ vars = Internal::Variant.make_from_set(where)
1110
+ Internal.wrap_oinky(Internal.oinky_index_cursor_seek_lower_bound(@hosthandle, @handle, vars.count, vars))
1111
+ self
1112
+ end
1113
+ end
1114
+ end #class Index
1115
+
1116
+ # By default, we never free cursors explicitly, which allows the GC to
1117
+ # free database instances more aggressively. These accessors allow the
1118
+ # bahavior to be changed. See the comment in the CursorBase initializer.
1119
+ @@implicit_cursor_cleanup = true
1120
+ def self.implicit_cursor_cleanup
1121
+ @@implicit_cursor_cleanup
1122
+ end
1123
+ def self.implicit_cursor_cleanup=(x)
1124
+ @@implicit_cursor_cleanup = x
1125
+ end
1126
+
1127
+ class DB
1128
+ attr_accessor :implicit_cursor_cleanup
1129
+ attr_reader :source_buffer
1130
+
1131
+ def initialize(ptr = nil, s = nil)
1132
+ if ptr
1133
+ unless ptr.is_a? FFI::Pointer
1134
+ raise OinkyException.new("Can't initialize oinky DB with given value of type: #{ptr.class}")
1135
+ end
1136
+ @db_ptr = ptr
1137
+ else
1138
+ # Create an empty DB
1139
+ @db_ptr = Internal.oinky_db_new
1140
+ end
1141
+ # We must preserve a reference to the heap-string object, at least until
1142
+ # we destroy the DB instance.
1143
+ @source_buffer = s
1144
+
1145
+ # Make an instance-local copy of the global default. Changing the global
1146
+ # after this point will not alter the behavior of cursors on this instance.
1147
+ @implicit_cursor_cleanup = Oinky.implicit_cursor_cleanup
1148
+
1149
+ ObjectSpace.define_finalizer( self, self.class.finalize(@db_ptr) )
1150
+ end
1151
+
1152
+ def self.finalize(ptr)
1153
+ proc { Internal.oinky_db_free(ptr) }
1154
+ end
1155
+
1156
+ # Deep copy is this simple
1157
+ def clone
1158
+ str = self.serialize
1159
+ self.class.mount(str)
1160
+ end
1161
+
1162
+ def self.mount(s)
1163
+ if s.is_a? String
1164
+ s = NativeMallocBuffer.new(s)
1165
+ end
1166
+ unless s.is_a? NativeBuffer
1167
+ raise OinkyException.new("Oinky can only mount a String or a NativeBuffer")
1168
+ end
1169
+ ptr = FFI::MemoryPointer.new :pointer
1170
+ Internal.wrap_oinky(Internal.oinky_db_mount(ptr, s.length, s.ptr))
1171
+ return DB.new(ptr.read_pointer, s)
1172
+ end
1173
+
1174
+ class Savepoint
1175
+ attr_reader :db, :value
1176
+ def initialize(db, value)
1177
+ @db, @value = db, value
1178
+ end
1179
+ end
1180
+
1181
+ def create_savepoint
1182
+ sp = FFI::MemoryPointer.new(:uint64)
1183
+ Internal.wrap_oinky(Internal.oinky_db_create_savepoint(@db_ptr, sp))
1184
+ return Savepoint.new(self, sp.read_uint64)
1185
+ end
1186
+
1187
+ def savepoint_rollback(sp)
1188
+ unless sp.is_a?(Savepoint) and (sp.db == self)
1189
+ raise OinkyException.new("ArgumentError. SP does not match this DB instance")
1190
+ end
1191
+ Internal.wrap_oinky(Internal.oinky_db_savepoint_rollback(@db_ptr, sp.value))
1192
+ self
1193
+ end
1194
+
1195
+ # This is intended to support a write cache.
1196
+ #
1197
+ # Determines whether the database has been modified since the given
1198
+ # savepoint. This can generate some false positives in the case where
1199
+ # the only relevant changes were rolled back to a former state, but
1200
+ # this transient ambiguity can be eliminated by creating a new savepoint
1201
+ # to test against, after each writeback.
1202
+ def modified_since?(sp)
1203
+ Internal.oinky_db_is_modified_since(@db_ptr, sp.value) != 0
1204
+ end
1205
+
1206
+ # Usage:
1207
+ #
1208
+ # db.atomic {
1209
+ # db.create_table 'foo'
1210
+ # db.create_table 'bar'
1211
+ # ...
1212
+ # }
1213
+ def atomic
1214
+ sp = self.create_savepoint
1215
+ begin
1216
+ yield
1217
+ sp = nil
1218
+ ensure
1219
+ self.savepoint_rollback(sp) if sp
1220
+ end
1221
+ self
1222
+ end
1223
+
1224
+ def table_count
1225
+ Internal.oinky_table_count(@db_ptr)
1226
+ end
1227
+
1228
+ def serialize
1229
+ size = Internal.prepare_pack(@db_ptr)
1230
+ str = NativeMallocBuffer.new(size)
1231
+ Internal.wrap_oinky(Internal.oinky_db_complete_pack(@db_ptr, str.length, str.ptr))
1232
+ str
1233
+ end
1234
+
1235
+ def create_table(name)
1236
+ handle = FFI::MemoryPointer.new :pointer
1237
+ Internal.wrap_oinky(Internal.oinky_db_create_table(@db_ptr, name.to_s, handle))
1238
+ tbl = Table.new(self, handle.read_pointer)
1239
+ if block_given?
1240
+ yield tbl
1241
+ end
1242
+ self
1243
+ end
1244
+
1245
+ def drop_table(name)
1246
+ name = name.name if name.is_a? Table
1247
+ dbs = Internal::DB_string.make(name.to_s)
1248
+ Internal.wrap_oinky(Internal.oinky_db_drop_table(@db_ptr, dbs))
1249
+ self
1250
+ end
1251
+
1252
+ def tables
1253
+ sz = self.table_count()
1254
+ handles = FFI::MemoryPointer.new(:pointer, sz)
1255
+ rsz = Internal.oinky_db_get_table_handles(@db_ptr, handles, sz)
1256
+ unless rsz == sz
1257
+ raise OinkyException.new("Oinky: Unknown interface error - tablecount")
1258
+ end
1259
+
1260
+ map = ExHash.new
1261
+ (0..sz-1).each{|i|
1262
+ th = Table.new(self, handles[i].read_pointer)
1263
+ map[th.name] = th
1264
+ }
1265
+ map.freeze
1266
+ end
1267
+
1268
+ def [](tblname)
1269
+ return tables[tblname]
1270
+ end
1271
+
1272
+ def with_table(tblname)
1273
+ yield self[tblname]
1274
+ return self
1275
+ end
1276
+
1277
+ def alter_table(tblname)
1278
+ self.atomic {
1279
+ yield self[tblname]
1280
+ }
1281
+ end
1282
+ end
1283
+ end
1284
+