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.
- data/LICENSE +22 -0
- data/README.md +141 -0
- data/ext/extconf.rb +79 -0
- data/ext/include/oinky.h +424 -0
- data/ext/include/oinky.hpp +63 -0
- data/ext/include/oinky/nky_base.hpp +1116 -0
- data/ext/include/oinky/nky_core.hpp +1603 -0
- data/ext/include/oinky/nky_cursor.hpp +665 -0
- data/ext/include/oinky/nky_dialect.hpp +107 -0
- data/ext/include/oinky/nky_error.hpp +164 -0
- data/ext/include/oinky/nky_fixed_table.hpp +710 -0
- data/ext/include/oinky/nky_handle.hpp +334 -0
- data/ext/include/oinky/nky_index.hpp +1038 -0
- data/ext/include/oinky/nky_log.hpp +15 -0
- data/ext/include/oinky/nky_merge_itr.hpp +403 -0
- data/ext/include/oinky/nky_model.hpp +110 -0
- data/ext/include/oinky/nky_pool.hpp +760 -0
- data/ext/include/oinky/nky_public.hpp +808 -0
- data/ext/include/oinky/nky_serializer.hpp +1625 -0
- data/ext/include/oinky/nky_strtable.hpp +504 -0
- data/ext/include/oinky/nky_table.hpp +1996 -0
- data/ext/nky_lib.cpp +390 -0
- data/ext/nky_lib_core.hpp +212 -0
- data/ext/nky_lib_index.cpp +158 -0
- data/ext/nky_lib_table.cpp +224 -0
- data/lib/oinky.rb +1284 -0
- data/lib/oinky/compiler.rb +106 -0
- data/lib/oinky/cpp_emitter.rb +311 -0
- data/lib/oinky/dsl.rb +167 -0
- data/lib/oinky/error.rb +19 -0
- data/lib/oinky/modelbase.rb +12 -0
- data/lib/oinky/nbuffer.rb +152 -0
- data/lib/oinky/normalize.rb +132 -0
- data/lib/oinky/oc_builder.rb +44 -0
- data/lib/oinky/query.rb +193 -0
- data/lib/oinky/rb_emitter.rb +147 -0
- data/lib/oinky/shard.rb +40 -0
- data/lib/oinky/testsup.rb +104 -0
- data/lib/oinky/version.rb +9 -0
- data/oinky.gemspec +36 -0
- 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
|
+
|
data/lib/oinky.rb
ADDED
@@ -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
|
+
|