oklahoma_mixer 0.2.0 → 0.3.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/CHANGELOG.rdoc +7 -0
- data/README.rdoc +4 -0
- data/Rakefile +1 -1
- data/TODO.rdoc +2 -3
- data/lib/oklahoma_mixer.rb +7 -20
- data/lib/oklahoma_mixer/array_list.rb +21 -7
- data/lib/oklahoma_mixer/array_list/c.rb +9 -1
- data/lib/oklahoma_mixer/b_tree_database.rb +25 -9
- data/lib/oklahoma_mixer/b_tree_database/c.rb +5 -0
- data/lib/oklahoma_mixer/cursor.rb +3 -0
- data/lib/oklahoma_mixer/cursor/c.rb +2 -0
- data/lib/oklahoma_mixer/error.rb +2 -0
- data/lib/oklahoma_mixer/extensible_string.rb +4 -2
- data/lib/oklahoma_mixer/extensible_string/c.rb +2 -0
- data/lib/oklahoma_mixer/fixed_length_database.rb +11 -3
- data/lib/oklahoma_mixer/fixed_length_database/c.rb +2 -0
- data/lib/oklahoma_mixer/hash_database.rb +24 -7
- data/lib/oklahoma_mixer/hash_database/c.rb +2 -0
- data/lib/oklahoma_mixer/hash_map.rb +42 -0
- data/lib/oklahoma_mixer/hash_map/c.rb +27 -0
- data/lib/oklahoma_mixer/query.rb +73 -0
- data/lib/oklahoma_mixer/query/c.rb +51 -0
- data/lib/oklahoma_mixer/table_database.rb +402 -0
- data/lib/oklahoma_mixer/table_database/c.rb +104 -0
- data/lib/oklahoma_mixer/utilities.rb +26 -1
- data/test/{b_tree_binary_data_test.rb → b_tree_database/b_tree_binary_data_test.rb} +2 -2
- data/test/{b_tree_tuning_test.rb → b_tree_database/b_tree_tuning_test.rb} +2 -2
- data/test/{cursor_based_iteration_test.rb → b_tree_database/cursor_based_iteration_test.rb} +2 -2
- data/test/{duplicate_storage_test.rb → b_tree_database/duplicate_storage_test.rb} +18 -12
- data/test/{binary_data_test.rb → core_database/binary_data_test.rb} +2 -2
- data/test/{file_system_test.rb → core_database/file_system_test.rb} +0 -0
- data/test/{getting_and_setting_keys_test.rb → core_database/getting_and_setting_keys_test.rb} +31 -60
- data/test/{iteration_test.rb → core_database/iteration_test.rb} +2 -2
- data/test/{transactions_test.rb → core_database/transactions_test.rb} +0 -0
- data/test/core_database/tuning_test.rb +31 -0
- data/test/{fixed_length_tuning_test.rb → fixed_length_database/fixed_length_tuning_test.rb} +0 -0
- data/test/{getting_and_setting_by_id_test.rb → fixed_length_database/getting_and_setting_by_id_test.rb} +8 -0
- data/test/{shared_binary_data.rb → shared/binary_data_tests.rb} +1 -1
- data/test/{tuning_test.rb → shared/hash_tuning_tests.rb} +18 -42
- data/test/{shared_iteration.rb → shared/iteration_tests.rb} +8 -7
- data/test/{key_range_test.rb → shared/key_range_test.rb} +0 -0
- data/test/{order_test.rb → shared/order_test.rb} +0 -0
- data/test/shared/storage_tests.rb +65 -0
- data/test/{top_level_interface_test.rb → shared/top_level_interface_test.rb} +39 -2
- data/test/{shared_tuning.rb → shared/tuning_tests.rb} +1 -1
- data/test/table_database/document_iteration_test.rb +22 -0
- data/test/table_database/document_storage_test.rb +225 -0
- data/test/table_database/index_test.rb +57 -0
- data/test/table_database/query_test.rb +866 -0
- data/test/table_database/table_tuning_test.rb +56 -0
- data/test/test_helper.rb +27 -0
- metadata +35 -36
@@ -0,0 +1,42 @@
|
|
1
|
+
require "oklahoma_mixer/hash_map/c"
|
2
|
+
|
3
|
+
module OklahomaMixer
|
4
|
+
class HashMap # :nodoc:
|
5
|
+
def initialize(pointer = C.new)
|
6
|
+
@pointer = pointer
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :pointer
|
10
|
+
|
11
|
+
def update(pairs)
|
12
|
+
pairs.each do |key, value|
|
13
|
+
C.put(@pointer, *[yield(key), yield(value)].flatten)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
C.iterinit(@pointer)
|
19
|
+
loop do
|
20
|
+
return self unless key = C.read_from_func(:iternext, :no_free, @pointer)
|
21
|
+
yield [key, C.read_from_func(:get, :no_free, @pointer, key, key.size)]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
hash = { }
|
27
|
+
each do |key, value|
|
28
|
+
hash[yield(key)] = yield(value)
|
29
|
+
end
|
30
|
+
hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace(pairs, &cast)
|
34
|
+
C.clear(@pointer)
|
35
|
+
update(pairs, &cast)
|
36
|
+
end
|
37
|
+
|
38
|
+
def free
|
39
|
+
C.del(@pointer)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "oklahoma_mixer/utilities"
|
2
|
+
|
3
|
+
module OklahomaMixer
|
4
|
+
class HashMap
|
5
|
+
module C # :nodoc:
|
6
|
+
extend OklahomaMixer::Utilities::FFIDSL
|
7
|
+
|
8
|
+
prefix :tcmap
|
9
|
+
|
10
|
+
def_new_and_del_funcs
|
11
|
+
|
12
|
+
func :name => :put,
|
13
|
+
:args => [:pointer, :pointer, :int, :pointer, :int]
|
14
|
+
func :name => :get,
|
15
|
+
:args => [:pointer, :pointer, :int, :pointer],
|
16
|
+
:returns => :pointer
|
17
|
+
func :name => :clear,
|
18
|
+
:args => :pointer
|
19
|
+
|
20
|
+
func :name => :iterinit,
|
21
|
+
:args => :pointer
|
22
|
+
func :name => :iternext,
|
23
|
+
:args => [:pointer, :pointer],
|
24
|
+
:returns => :pointer
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "oklahoma_mixer/error"
|
2
|
+
require "oklahoma_mixer/query/c"
|
3
|
+
|
4
|
+
module OklahomaMixer
|
5
|
+
class Query # :nodoc:
|
6
|
+
def initialize(table_pointer)
|
7
|
+
@pointer = C.new(table_pointer)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :pointer
|
11
|
+
|
12
|
+
def condition(column, operator, expression, no_index = false)
|
13
|
+
operator = operator.to_s
|
14
|
+
negate = operator.sub!(/\A(?:!_?|not_)/, "")
|
15
|
+
operator = case operator
|
16
|
+
when /\A(?:=?=|eq(?:ua)?ls?\??)\z/
|
17
|
+
if expression.is_a? Numeric then :TDBQCNUMEQ
|
18
|
+
else :TDBQCSTREQ
|
19
|
+
end
|
20
|
+
when /\Astr(?:ing)?_eq(?:ua)?ls?\??\z/ then :TDBQCSTREQ
|
21
|
+
when /\Ainclude?s?\??\z/ then :TDBQCSTRINC
|
22
|
+
when /\Astarts?_with\??\z/ then :TDBQCSTRBW
|
23
|
+
when /\Aends?_with\??\z/ then :TDBQCSTREW
|
24
|
+
when /\Aincludes?_all(?:_tokens?)?\??\z/ then :TDBQCSTRAND
|
25
|
+
when /\Aincludes?_any(?:_tokens?)?\??\z/ then :TDBQCSTROR
|
26
|
+
when /\Aeq(?:ua)?ls?_any(?:_tokens?)?\??\z/ then :TDBQCSTROREQ
|
27
|
+
when /\A(?:~|=~|match(?:es)?\??)\z/ then :TDBQCSTRRX
|
28
|
+
when /\Anum(?:ber)?_eq(?:ua)?ls?\??\z/ then :TDBQCNUMEQ
|
29
|
+
when ">" then :TDBQCNUMGT
|
30
|
+
when ">=" then :TDBQCNUMGE
|
31
|
+
when "<" then :TDBQCNUMLT
|
32
|
+
when "<=" then :TDBQCNUMLE
|
33
|
+
when /\Abetween\??\z/ then :TDBQCNUMBT
|
34
|
+
when /\Aany_num(?:ber)?\??\z/ then :TDBQCNUMOREQ
|
35
|
+
when /\Aphrase_search\??\z/ then :TDBQCFTSPH
|
36
|
+
when /\Aall_tokens?_search\??\z/ then :TDBQCFTSAND
|
37
|
+
when /\Aany_tokens?_search\??\z/ then :TDBQCFTSOR
|
38
|
+
when /\Aexpression_search\??\z/ then :TDBQCFTSEX
|
39
|
+
else
|
40
|
+
fail Error::QueryError, "unknown condition operator"
|
41
|
+
end
|
42
|
+
operator = C::CONDITIONS[operator]
|
43
|
+
operator |= C::CONDITIONS[:TDBQCNEGATE] if negate
|
44
|
+
operator |= C::CONDITIONS[:TDBQCNOIDX] if no_index
|
45
|
+
C.addcond( @pointer,
|
46
|
+
yield(column),
|
47
|
+
operator,
|
48
|
+
yield( expression.respond_to?(:source) ?
|
49
|
+
expression.source :
|
50
|
+
expression ) )
|
51
|
+
end
|
52
|
+
|
53
|
+
def order(column, str_num_asc_desc = nil)
|
54
|
+
str_num_asc_desc = case str_num_asc_desc.to_s
|
55
|
+
when /\A(?:STR_)?ASC\z/i, "" then :TDBQOSTRASC
|
56
|
+
when /\A(?:STR_)?DESC\z/i then :TDBQOSTRDESC
|
57
|
+
when /\ANUM_ASC\z/i then :TDBQONUMASC
|
58
|
+
when /\ANUM_DESC\z/i then :TDBQONUMDESC
|
59
|
+
else
|
60
|
+
fail Error::QueryError, "unknown order type"
|
61
|
+
end
|
62
|
+
C.setorder(@pointer, yield(column), C::ORDERS[str_num_asc_desc])
|
63
|
+
end
|
64
|
+
|
65
|
+
def limit(max, offset = nil)
|
66
|
+
C.setlimit(@pointer, max.to_i, offset.to_i)
|
67
|
+
end
|
68
|
+
|
69
|
+
def free
|
70
|
+
C.del(@pointer)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "oklahoma_mixer/utilities"
|
2
|
+
|
3
|
+
module OklahomaMixer
|
4
|
+
class Query
|
5
|
+
module C # :nodoc:
|
6
|
+
extend OklahomaMixer::Utilities::FFIDSL
|
7
|
+
|
8
|
+
prefix :tctdbqry
|
9
|
+
|
10
|
+
CONDITIONS = enum :TDBQCSTREQ, 0,
|
11
|
+
:TDBQCSTRINC, 1,
|
12
|
+
:TDBQCSTRBW, 2,
|
13
|
+
:TDBQCSTREW, 3,
|
14
|
+
:TDBQCSTRAND, 4,
|
15
|
+
:TDBQCSTROR, 5,
|
16
|
+
:TDBQCSTROREQ, 6,
|
17
|
+
:TDBQCSTRRX, 7,
|
18
|
+
:TDBQCNUMEQ, 8,
|
19
|
+
:TDBQCNUMGT, 9,
|
20
|
+
:TDBQCNUMGE, 10,
|
21
|
+
:TDBQCNUMLT, 11,
|
22
|
+
:TDBQCNUMLE, 12,
|
23
|
+
:TDBQCNUMBT, 13,
|
24
|
+
:TDBQCNUMOREQ, 14,
|
25
|
+
:TDBQCFTSPH, 15,
|
26
|
+
:TDBQCFTSAND, 16,
|
27
|
+
:TDBQCFTSOR, 17,
|
28
|
+
:TDBQCFTSEX, 18,
|
29
|
+
:TDBQCNEGATE, 1 << 24,
|
30
|
+
:TDBQCNOIDX, 1 << 25
|
31
|
+
ORDERS = enum :TDBQOSTRASC, 0,
|
32
|
+
:TDBQOSTRDESC, 1,
|
33
|
+
:TDBQONUMASC, 2,
|
34
|
+
:TDBQONUMDESC, 3
|
35
|
+
|
36
|
+
|
37
|
+
func :name => :new,
|
38
|
+
:args => :pointer,
|
39
|
+
:returns => :pointer
|
40
|
+
func :name => :del,
|
41
|
+
:args => :pointer
|
42
|
+
|
43
|
+
func :name => :addcond,
|
44
|
+
:args => [:pointer, :string, CONDITIONS, :string]
|
45
|
+
func :name => :setorder,
|
46
|
+
:args => [:pointer, :string, ORDERS]
|
47
|
+
func :name => :setlimit,
|
48
|
+
:args => [:pointer, :int, :int]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require "oklahoma_mixer/error"
|
2
|
+
require "oklahoma_mixer/array_list"
|
3
|
+
require "oklahoma_mixer/hash_map"
|
4
|
+
require "oklahoma_mixer/query"
|
5
|
+
require "oklahoma_mixer/hash_database"
|
6
|
+
require "oklahoma_mixer/table_database/c"
|
7
|
+
|
8
|
+
module OklahomaMixer
|
9
|
+
class TableDatabase < HashDatabase
|
10
|
+
module Paginated
|
11
|
+
attr_accessor :current_page, :per_page, :total_entries
|
12
|
+
|
13
|
+
def total_pages
|
14
|
+
(total_entries / per_page.to_f).ceil
|
15
|
+
end
|
16
|
+
|
17
|
+
def out_of_bounds?
|
18
|
+
current_page > total_pages
|
19
|
+
end
|
20
|
+
|
21
|
+
def offset
|
22
|
+
(current_page - 1) * per_page
|
23
|
+
end
|
24
|
+
|
25
|
+
def previous_page
|
26
|
+
current_page > 1 ? (current_page - 1) : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_page
|
30
|
+
current_page < total_pages ? (current_page + 1) : nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
################################
|
35
|
+
### Getting and Setting Keys ###
|
36
|
+
################################
|
37
|
+
|
38
|
+
def store(key, value, mode = nil, &dup_handler)
|
39
|
+
if mode == :add and dup_handler.nil?
|
40
|
+
super
|
41
|
+
elsif dup_handler
|
42
|
+
warn "block supersedes mode argument" unless mode.nil?
|
43
|
+
k = cast_key_in(key)
|
44
|
+
callback = lambda { |old_value_pointer, old_size, returned_size, _|
|
45
|
+
old_value = cast_from_null_terminated_colums(
|
46
|
+
*old_value_pointer.get_bytes(0, old_size)
|
47
|
+
)
|
48
|
+
replacement, size = cast_to_null_terminated_colums( yield( key,
|
49
|
+
old_value,
|
50
|
+
value ) )
|
51
|
+
returned_size.put_int(0, size)
|
52
|
+
pointer = Utilities.malloc(size)
|
53
|
+
pointer.put_bytes(0, replacement) unless pointer.address.zero?
|
54
|
+
pointer
|
55
|
+
}
|
56
|
+
try(:putproc, k, cast_to_null_terminated_colums(value), callback, nil)
|
57
|
+
value
|
58
|
+
else
|
59
|
+
Utilities.temp_map do |map|
|
60
|
+
map.update(value) { |string|
|
61
|
+
cast_to_bytes_and_length(string)
|
62
|
+
}
|
63
|
+
result = super(key, map, mode, &dup_handler)
|
64
|
+
result == map ? value : result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
alias_method :[]=, :store
|
69
|
+
|
70
|
+
def fetch(key, *default)
|
71
|
+
if value = try( :get, cast_key_in(key),
|
72
|
+
:failure => lambda { |value| value.address.zero? },
|
73
|
+
:no_error => {22 => nil} )
|
74
|
+
cast_value_out(value)
|
75
|
+
else
|
76
|
+
if block_given?
|
77
|
+
warn "block supersedes default value argument" unless default.empty?
|
78
|
+
yield key
|
79
|
+
elsif not default.empty?
|
80
|
+
default.first
|
81
|
+
else
|
82
|
+
fail IndexError, "key not found"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate_unique_id
|
88
|
+
try(:genuid, :failure => -1)
|
89
|
+
end
|
90
|
+
alias_method :uid, :generate_unique_id
|
91
|
+
|
92
|
+
#################
|
93
|
+
### Iteration ###
|
94
|
+
#################
|
95
|
+
|
96
|
+
def each
|
97
|
+
try(:iterinit)
|
98
|
+
loop do
|
99
|
+
pointer = try( :iternext3,
|
100
|
+
:failure => lambda { |value| value.address.zero? },
|
101
|
+
:no_error => {22 => nil} )
|
102
|
+
return self unless pointer
|
103
|
+
value = cast_value_out(pointer)
|
104
|
+
key = value.delete("")
|
105
|
+
yield [key, value]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
alias_method :each_pair, :each
|
109
|
+
|
110
|
+
###############
|
111
|
+
### Queries ###
|
112
|
+
###############
|
113
|
+
|
114
|
+
def all(options = { }, &iterator)
|
115
|
+
query(options) do |q|
|
116
|
+
mode = results_mode(options)
|
117
|
+
if block_given?
|
118
|
+
results = self
|
119
|
+
callback = lambda { |key_pointer, key_size, doc_map, _|
|
120
|
+
if mode != :docs
|
121
|
+
key = cast_key_out(key_pointer.get_bytes(0, key_size))
|
122
|
+
end
|
123
|
+
if mode != :keys
|
124
|
+
map = HashMap.new(doc_map)
|
125
|
+
doc = map.to_hash { |string| cast_to_encoded_string(string) }
|
126
|
+
end
|
127
|
+
flags = case mode
|
128
|
+
when :keys then yield(key)
|
129
|
+
when :docs then yield(doc)
|
130
|
+
else yield(key, doc)
|
131
|
+
end
|
132
|
+
Array(flags).inject(0) { |returned_flags, flag|
|
133
|
+
returned_flags | case flag.to_s
|
134
|
+
when "update"
|
135
|
+
if mode != :keys
|
136
|
+
map.replace(doc) { |key_or_value|
|
137
|
+
cast_to_bytes_and_length(key_or_value)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
lib::FLAGS[:TDBQPPUT]
|
141
|
+
when "delete" then lib::FLAGS[:TDBQPOUT]
|
142
|
+
when "break" then lib::FLAGS[:TDBQPSTOP]
|
143
|
+
else 0
|
144
|
+
end
|
145
|
+
}
|
146
|
+
}
|
147
|
+
else
|
148
|
+
results = mode != :hoh ? [ ] : { }
|
149
|
+
callback = lambda { |key_pointer, key_size, doc_map, _|
|
150
|
+
if mode == :docs
|
151
|
+
results << cast_value_out(doc_map, :no_free)
|
152
|
+
else
|
153
|
+
key = cast_key_out(key_pointer.get_bytes(0, key_size))
|
154
|
+
case mode
|
155
|
+
when :keys
|
156
|
+
results << key
|
157
|
+
when :hoh
|
158
|
+
results[key] = cast_value_out(doc_map, :no_free)
|
159
|
+
when :aoh
|
160
|
+
results << cast_value_out(doc_map, :no_free).
|
161
|
+
merge(:primary_key => key)
|
162
|
+
else
|
163
|
+
results << [key, cast_value_out(doc_map, :no_free)]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
0
|
167
|
+
}
|
168
|
+
end
|
169
|
+
lib.qryproc(q.pointer, callback, nil)
|
170
|
+
results
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def first(options = { })
|
175
|
+
all(options.merge(:limit => 1)).first
|
176
|
+
end
|
177
|
+
|
178
|
+
def count(options = { })
|
179
|
+
count = 0
|
180
|
+
all(options) { count += 1 }
|
181
|
+
count
|
182
|
+
end
|
183
|
+
|
184
|
+
def paginate(options)
|
185
|
+
mode = results_mode(options)
|
186
|
+
results = (mode != :hoh ? [ ] : { }).extend(Paginated)
|
187
|
+
fail Error::QueryError, ":page argument required" \
|
188
|
+
unless options.include? :page
|
189
|
+
results.current_page = (options[:page] || 1).to_i
|
190
|
+
fail Error::QueryError, ":page must be >= 1" if results.current_page < 1
|
191
|
+
results.per_page = (options[:per_page] || 30).to_i
|
192
|
+
fail Error::QueryError, ":per_page must be >= 1" if results.per_page < 1
|
193
|
+
results.total_entries = 0
|
194
|
+
all( options.merge( :select => :keys_and_docs,
|
195
|
+
:limit => nil ) ) { |key, value|
|
196
|
+
if results.total_entries >= results.offset and
|
197
|
+
results.size < results.per_page
|
198
|
+
case mode
|
199
|
+
when :keys
|
200
|
+
results << key
|
201
|
+
when :docs
|
202
|
+
results << value
|
203
|
+
when :hoh
|
204
|
+
results[key] = value
|
205
|
+
when :aoh
|
206
|
+
results << value.merge(:primary_key => key)
|
207
|
+
else
|
208
|
+
results << [key, value]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
results.total_entries += 1
|
212
|
+
}
|
213
|
+
results
|
214
|
+
end
|
215
|
+
|
216
|
+
def union(q, *queries)
|
217
|
+
search([q] + queries, lib::SEARCHES[:TDBMSUNION])
|
218
|
+
end
|
219
|
+
|
220
|
+
def intersection(q, *queries)
|
221
|
+
search([q] + queries, lib::SEARCHES[:TDBMSISECT])
|
222
|
+
end
|
223
|
+
alias_method :isect, :intersection
|
224
|
+
|
225
|
+
def difference(q, *queries)
|
226
|
+
search([q] + queries, lib::SEARCHES[:TDBMSDIFF])
|
227
|
+
end
|
228
|
+
alias_method :diff, :difference
|
229
|
+
|
230
|
+
###############
|
231
|
+
### Indexes ###
|
232
|
+
###############
|
233
|
+
|
234
|
+
def add_index(column, type, keep = false)
|
235
|
+
type = case type.to_s
|
236
|
+
when "lexical", "string" then lib::INDEXES[:TDBITLEXICAL]
|
237
|
+
when "decimal", "numeric" then lib::INDEXES[:TDBITDECIMAL]
|
238
|
+
when "token" then lib::INDEXES[:TDBITTOKEN]
|
239
|
+
when "qgram" then lib::INDEXES[:TDBITQGRAM]
|
240
|
+
else
|
241
|
+
fail Error::IndexError, "unknown index type"
|
242
|
+
end
|
243
|
+
type |= lib::INDEXES[:TDBITKEEP] if keep
|
244
|
+
try( :setindex,
|
245
|
+
cast_to_bytes_and_length(column_name(column)).first,
|
246
|
+
type,
|
247
|
+
:no_error => {21 => false} )
|
248
|
+
end
|
249
|
+
|
250
|
+
def remove_index(column)
|
251
|
+
try( :setindex,
|
252
|
+
cast_to_bytes_and_length(column_name(column)).first,
|
253
|
+
lib::INDEXES[:TDBITVOID],
|
254
|
+
:no_error => {2 => false} )
|
255
|
+
end
|
256
|
+
|
257
|
+
def optimize_index(column)
|
258
|
+
try( :setindex,
|
259
|
+
cast_to_bytes_and_length(column_name(column)).first,
|
260
|
+
lib::INDEXES[:TDBITOPT],
|
261
|
+
:no_error => {2 => false} )
|
262
|
+
end
|
263
|
+
alias_method :defrag_index, :optimize_index
|
264
|
+
|
265
|
+
#######
|
266
|
+
private
|
267
|
+
#######
|
268
|
+
|
269
|
+
def tune(options)
|
270
|
+
super
|
271
|
+
if options.values_at(:bnum, :apow, :fpow, :opts).any?
|
272
|
+
optimize(options.merge(:tune => true))
|
273
|
+
end
|
274
|
+
if options.values_at(:rcnum, :lcnum, :ncnum).any?
|
275
|
+
setcache(options)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def setcache(options)
|
280
|
+
try( :setcache,
|
281
|
+
options.fetch(:rcnum, 0).to_i,
|
282
|
+
options.fetch(:lcnum, 0).to_i,
|
283
|
+
options.fetch(:ncnum, 0).to_i )
|
284
|
+
end
|
285
|
+
|
286
|
+
def cast_value_in(value)
|
287
|
+
value.pointer
|
288
|
+
end
|
289
|
+
|
290
|
+
def cast_value_out(pointer, no_free = false)
|
291
|
+
map = HashMap.new(pointer)
|
292
|
+
map.to_hash { |string| cast_to_encoded_string(string) }
|
293
|
+
ensure
|
294
|
+
map.free if map and not no_free
|
295
|
+
end
|
296
|
+
|
297
|
+
def cast_from_null_terminated_colums(string)
|
298
|
+
Hash[*string.split("\0").map { |s| cast_to_encoded_string(s) }]
|
299
|
+
end
|
300
|
+
|
301
|
+
def cast_to_null_terminated_colums(hash)
|
302
|
+
cast_to_bytes_and_length(hash.to_a.flatten.join("\0"))
|
303
|
+
end
|
304
|
+
|
305
|
+
def column_name(column)
|
306
|
+
case column
|
307
|
+
when :primary_key, :pk then ""
|
308
|
+
else column
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def query(options = { })
|
313
|
+
query = Query.new(@db)
|
314
|
+
conditions = Array(options[:conditions])
|
315
|
+
conditions = [conditions] unless conditions.empty? or
|
316
|
+
conditions.first.is_a? Array
|
317
|
+
conditions.each do |condition|
|
318
|
+
fail Error::QueryError,
|
319
|
+
"condition must be column, operator, and expression" \
|
320
|
+
unless condition.size.between? 3, 4
|
321
|
+
query.condition( column_name(condition.first),
|
322
|
+
*condition[1..-1] ) { |string|
|
323
|
+
cast_to_bytes_and_length(string).first
|
324
|
+
}
|
325
|
+
end
|
326
|
+
unless options[:order].nil?
|
327
|
+
order = options[:order] == "" ? [""] : Array(options[:order])
|
328
|
+
fail Error::QueryError, "order must have a field and can have a type" \
|
329
|
+
unless order.size.between? 1, 2
|
330
|
+
order[0] = column_name(order[0])
|
331
|
+
query.order(*order) { |string|
|
332
|
+
cast_to_bytes_and_length(string).first
|
333
|
+
}
|
334
|
+
end
|
335
|
+
unless options[:limit].nil?
|
336
|
+
query.limit(options[:limit], options[:offset])
|
337
|
+
end
|
338
|
+
if block_given?
|
339
|
+
yield query
|
340
|
+
else
|
341
|
+
query
|
342
|
+
end
|
343
|
+
ensure
|
344
|
+
query.free if query and block_given?
|
345
|
+
end
|
346
|
+
|
347
|
+
def results_mode(options)
|
348
|
+
case options[:select].to_s
|
349
|
+
when /\A(?:primary_)?keys?\z/i then :keys
|
350
|
+
when /\Adoc(?:ument)?s?\z/i then :docs
|
351
|
+
else
|
352
|
+
case options[:return].to_s
|
353
|
+
when /\Ah(?:ash_)?o(?:f_)?h(?:ash)?e?s?\z/i then :hoh
|
354
|
+
when /\Aa(?:rray_)?o(?:f_)?a(?:rray)?s?\z/i then :aoa
|
355
|
+
when /\Aa(?:rray_)?o(?:f_)?h(?:ash)?e?s?\z/i then :aoh
|
356
|
+
else
|
357
|
+
if RUBY_VERSION < "1.9" and not options[:order].nil? then :aoa
|
358
|
+
else :hoh
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def search(queries, operation)
|
365
|
+
mode = results_mode(queries.first)
|
366
|
+
qs = queries.map { |q| query(q) }
|
367
|
+
keys = ArrayList.new( Utilities.temp_pointer(qs.size) do |pointer|
|
368
|
+
pointer.write_array_of_pointer(qs.map { |q| q.pointer })
|
369
|
+
lib.metasearch(pointer, qs.size, operation)
|
370
|
+
end )
|
371
|
+
case mode
|
372
|
+
when :keys
|
373
|
+
keys.map { |key| cast_key_out(key) }
|
374
|
+
when :docs
|
375
|
+
keys.map { |key| self[cast_key_out(key)] }
|
376
|
+
when :hoh
|
377
|
+
results = { }
|
378
|
+
while key = keys.shift { |k| cast_key_out(k) }
|
379
|
+
results[key] = self[key]
|
380
|
+
end
|
381
|
+
results
|
382
|
+
when :aoh
|
383
|
+
keys.map { |key|
|
384
|
+
key = cast_key_out(key)
|
385
|
+
self[key].merge(:primary_key => key)
|
386
|
+
}
|
387
|
+
else
|
388
|
+
keys.map { |key|
|
389
|
+
key = cast_key_out(key)
|
390
|
+
[key, self[key]]
|
391
|
+
}
|
392
|
+
end
|
393
|
+
ensure
|
394
|
+
if qs
|
395
|
+
qs.each do |q|
|
396
|
+
q.free
|
397
|
+
end
|
398
|
+
end
|
399
|
+
keys.free if keys
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|