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
|