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,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
+