beezwax 0.6.0 → 0.6.1

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.6.1
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{beezwax}
8
- s.version = "0.6.0"
8
+ s.version = "0.6.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Patrick Lardin"]
12
- s.date = %q{2010-12-19}
12
+ s.date = %q{2010-12-20}
13
13
  s.description = %q{Access Btrieve database files through a ruby API.}
14
14
  s.email = %q{plardin@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -18,13 +18,18 @@ module BtrCodes
18
18
  BEGIN_TRANSACTION = 19
19
19
  END_TRANSACTION = 20
20
20
  ABORT_TRANSACTION = 21
21
+ GET_POSITION = 22
22
+ GET_DIRECT = 23
21
23
  STEP_NEXT = 24
22
24
  STOP = 25
23
25
  RESET = 28
24
26
  GET_EQUAL_KEY = 55
25
27
  GET_NEXT_KEY = 56
26
28
  POS_BLOCK_SIZE = 128
29
+ RECORD_POSITION_SIZE = 4
27
30
  NULL_KEY = 0x00
31
+ NO_LOGICAL_CURRENCY_KEY = -1
32
+ SYSTEM_LOG_KEY = 125
28
33
  NULL_BUFFER = ''
29
34
  NORMAL_MODE = 0x00
30
35
  ACCELERATED_MODE = 0xFF
@@ -32,7 +32,7 @@ module Btrieve
32
32
 
33
33
  # Utility method to create a buffer of a given size.
34
34
  def self.create_string_buffer(size)
35
- 0.chr*(size)
35
+ (0.chr*(size)).force_encoding("ASCII-8BIT")
36
36
  end
37
37
  end
38
38
 
@@ -4,26 +4,36 @@ require 'digest/md5'
4
4
  class BtrieveRecord
5
5
  include Btrieve
6
6
  attr_reader :data_buffer, :btrieve_table
7
+ attr_accessor :position
7
8
 
8
9
  # Initializes a btrieve record.
9
10
  def initialize(btrieve_table, data_buffer=nil)
10
11
  @btrieve_table = btrieve_table
11
12
  @data_buffer = data_buffer.nil? ? Btrieve.create_string_buffer(@btrieve_table.schema[:record_size]) : data_buffer
13
+ reset_physical_position
12
14
  end
13
15
 
14
16
  # Inserts a new btrieve record through the transactional BTR engine.
15
17
  def insert
16
- btr_op(@btrieve_table.session, INSERT, @btrieve_table.pos_buffer, @data_buffer, NULL_BUFFER, NO_CURRENCY_CHANGE)
18
+ btr_op(INSERT, @btrieve_table.pos_buffer, @data_buffer, NULL_BUFFER, SYSTEM_LOG_KEY)
19
+ set_physical_position
17
20
  end
18
21
 
19
22
  # Updates an existing btrieve record through the transactional BTR engine.
20
23
  def update
21
- btr_op(@btrieve_table.session, UPDATE, @btrieve_table.pos_buffer, @data_buffer, NULL_BUFFER, NO_CURRENCY_CHANGE)
24
+ new_state="#{@data_buffer}"
25
+ @data_buffer[0..3]=@position
26
+ btr_op(GET_DIRECT, @btrieve_table.pos_buffer, @data_buffer, NULL_BUFFER, SYSTEM_LOG_KEY)
27
+ btr_op(UPDATE, @btrieve_table.pos_buffer, new_state, NULL_BUFFER, SYSTEM_LOG_KEY)
28
+ set_physical_position
22
29
  end
23
30
 
24
31
  # Deletes an existing btrieve record through the transactional BTR engine.
25
32
  def delete
26
- btr_op(@btrieve_table.session, DELETE, @btrieve_table.pos_buffer, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
33
+ @data_buffer[0..3]=@position
34
+ btr_op(GET_DIRECT, @btrieve_table.pos_buffer, @data_buffer, NULL_BUFFER, SYSTEM_LOG_KEY)
35
+ btr_op(DELETE, @btrieve_table.pos_buffer, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
36
+ reset_physical_position
27
37
  end
28
38
 
29
39
  # Returns hash of column-value pairs for this btrieve record, given an array of named columns.
@@ -34,6 +44,11 @@ class BtrieveRecord
34
44
  end
35
45
  column_names.inject({}){|vals, key| vals[key]=self[key]; vals}
36
46
  end
47
+
48
+ # Sets the column valuesof this record based on the values passed through the 'column_values' map.
49
+ def values=(column_values)
50
+ column_values.each{ |key, value| self[key]=value }
51
+ end
37
52
 
38
53
  # Returns this record's primary key value. Supports composite keys.
39
54
  def primary_key()
@@ -44,38 +59,53 @@ class BtrieveRecord
44
59
  def [](key_name)
45
60
  column_info = get_column_info(key_name)
46
61
  return nil if(column_info.nil?)
47
- @data_buffer.unpack(column_info[:unpacker])[0]
62
+ val = @data_buffer.unpack(column_info[:unpacker])[0]
63
+ val = val.strip if val.respond_to?('strip')
64
+ val
48
65
  end
49
66
 
50
67
  # Sets this record's value for a particular column.
51
68
  def []=(key_name, value)
52
- schema_key = get_schema_key(key_name)
69
+ schema_key = get_column_info(key_name)
53
70
  packer_string = schema_key[:unpacker]
54
71
  match = packer_string.match(/@(\d+)([a-zA-Z]+)(\d*)/)
55
72
  offset = match[1].to_i
56
73
  datatype = match[2]
57
- thesizeof = BtrieveSchema.sizeof(datatype)
74
+ thesizeof = BtrieveSchema::SIZEOF[datatype]
58
75
  thesizeof *= match[3].empty? ? 1 : match[3].to_i
59
76
  packer = "#{datatype}#{thesizeof}"
60
77
  array = [value]
61
78
  packed_value = array.pack(packer)
62
79
  range = (offset..offset+packed_value.size-1)
63
- @data_buffer[range] = packed_value
80
+ data_buffer[range] = packed_value
64
81
  end
65
82
 
83
+ # Determines if a record is nil.
66
84
  def nil?()
67
- data_buffer.gsub("\x00", "").size==0
85
+ @data_buffer.gsub("\x00", "").size==0
68
86
  end
69
87
 
70
88
  # Returns a string representation of this record.
71
89
  def to_s()
72
- @btrieve_table.schema[:columns].keys.inject(''){|pretty_print, key| pretty_print = "#{pretty_print} #{key}=>#{self[key]}"; pretty_print}
90
+ @btrieve_table.schema[:columns].keys.inject("[position=>'#{position.unpack('i')[0]}'"){|pretty_print, key| pretty_print << ", #{key}=>'#{self[key]}'"; pretty_print } << "]"
73
91
  end
74
92
 
75
93
  # Returns this record's version as a MD5 Digest value.
76
94
  def version()
77
95
  Digest::MD5.hexdigest(@data_buffer)
78
96
  end
97
+
98
+ # TODO - documentation
99
+ def set_physical_position
100
+ reset_physical_position
101
+ btr_op(GET_POSITION, @btrieve_table.pos_buffer, @position, NULL_BUFFER, NULL_KEY)
102
+ self
103
+ end
104
+
105
+ # TODO - documentation
106
+ def reset_physical_position
107
+ @position = Btrieve.create_string_buffer(RECORD_POSITION_SIZE)
108
+ end
79
109
 
80
110
  private
81
111
 
@@ -28,20 +28,40 @@ class BtrieveSession
28
28
  @@instance=nil
29
29
  end
30
30
 
31
- # Executes a block of BTR directive in a transaction.
31
+ # Wraps and executes the block passed in inside a transaction.
32
+ # The transaction is aborted if the block raises an exception.
32
33
  def BtrieveSession.transaction(&block)
33
34
  begin
34
- @@instance.btr_op(BEGIN_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
35
+ begin_transaction
35
36
  yield
37
+ end_transaction
36
38
  rescue Exception => exception
37
39
  # Oops! We must roll back the whole shebang!
38
- @@instance.btr_op(ABORT_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
40
+ abort_transaction
39
41
  raise "PSQL TX ABORTED!!! - #{exception.message}\n#{exception.backtrace.join("\n\t")}"
40
- ensure
41
- @@instance.btr_op(END_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
42
42
  end
43
43
  end
44
44
 
45
+ # Delineates the begining of a transaction boundary. If a client calls this method explicitely,
46
+ # the client also has the responsibility of explicitely ending the transaction once done,
47
+ # or abort the transaction if things did not go well.
48
+ def BtrieveSession.begin_transaction()
49
+ @@instance.btr_op(BEGIN_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
50
+ end
51
+
52
+ # Delineates the end of a transaction boundary. The client has the responsibility of calling this method
53
+ # when a transaction was started _without_ a block.
54
+ def BtrieveSession.end_transaction()
55
+ @@instance.btr_op(END_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
56
+ end
57
+
58
+ # Aborts a transaction when something goes wrong. The client has the responsibility of calling this method
59
+ # when a transaction was started _without_ a block AND some unexpected event occured and the client wants
60
+ # to prevent any changes to the persistence to be commited.
61
+ def BtrieveSession.abort_transaction()
62
+ @@instance.btr_op(ABORT_TRANSACTION, NULL_BUFFER, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
63
+ end
64
+
45
65
  def set_schema(schema)
46
66
  @btrieve_schema=schema
47
67
  end
@@ -47,6 +47,8 @@ class BtrieveTable
47
47
  # Finds an array of records in this table, using the specified index number, a key and a range of values.
48
48
  # If a block is passed in, the entries of this set are passed into this block one by one. Otherwise,
49
49
  # the set is returned to the caller.
50
+ # WARNING - NO UNIT TEST EXIST!
51
+ # TODO - Add a unit test for this method.
50
52
  def find_in_range(index_num, key, range, &block)
51
53
  index = @schema[:indices][index_num]
52
54
  columns = @schema[:columns]
@@ -80,6 +82,7 @@ class BtrieveTable
80
82
  # Closes this btrieve table to conclude a sequence of BTR operation(s).
81
83
  def close
82
84
  btr_op(CLOSE, @pos_buffer, NULL_BUFFER, NULL_BUFFER, NULL_KEY)
85
+ @pos_buffer=Btrieve.create_string_buffer(POS_BLOCK_SIZE)
83
86
  end
84
87
 
85
88
  # Loops over the records of this BTR table and passes each record to the block passed in.
@@ -151,8 +154,11 @@ class BtrieveTable
151
154
  def find_in_unique_index(match, index_number=nil)
152
155
  finder(match, index_number) do |key_buffer, index|
153
156
  btr_record = BtrieveRecord.new(self)
154
- batch(){ btr_op(GET_EQUAL, @pos_buffer, btr_record.data_buffer, key_buffer, index, [KEY_NOT_FOUND, OK, EOF]) }
155
- btr_record.nil? ? nil : btr_record
157
+ open
158
+ btr_op(GET_EQUAL, @pos_buffer, btr_record.data_buffer, key_buffer, index, [KEY_NOT_FOUND, OK, EOF])
159
+ btr_record = btr_record.nil? ? nil : btr_record.set_physical_position
160
+ close
161
+ btr_record
156
162
  end
157
163
  end
158
164
 
@@ -170,7 +176,7 @@ class BtrieveTable
170
176
  ops_result = btr_op(ops, @pos_buffer, btr_record.data_buffer, key_buffer, index, [OK, EOF])
171
177
  break if(key_buffer!=org_key_buffer)
172
178
  ops=GET_NEXT
173
- result << btr_record if(ops_result == OK)
179
+ result << btr_record.set_physical_position if(ops_result == OK)
174
180
  end
175
181
  end
176
182
  result
@@ -182,6 +188,6 @@ class BtrieveTable
182
188
  raise "Cannot manipulate a BtrieveTable when no BtrieveSession exists." if s.nil?
183
189
  s
184
190
  end
185
-
191
+
186
192
  end
187
193
 
@@ -15,22 +15,50 @@ class TestBeezwax < Test::Unit::TestCase
15
15
  end
16
16
 
17
17
  should "return a handle to the session singleton" do
18
- session=BtrieveSession.get_session
18
+ session=nil
19
+ assert_nothing_raised{session=BtrieveSession.get_session}
19
20
  assert_not_nil session
20
21
  end
21
22
 
22
23
  should "permit the instanciation of a DB table" do
23
- table=BtrieveTable.new(:course)
24
+ table=nil
25
+ assert_nothing_raised{table=BtrieveTable.new(:course)}
24
26
  assert_not_nil table
25
27
  end
26
28
 
29
+ should "permit the instanciation of multiple instances for the same DB table" do
30
+ table0 = table1 = nil
31
+ assert_nothing_raised do
32
+ table0=BtrieveTable.new(:course)
33
+ table1=BtrieveTable.new(:course)
34
+ end
35
+ assert_not_nil table0
36
+ assert_not_nil table1
37
+ end
38
+
27
39
  should "allow an empty transaction to go through" do
28
- BtrieveSession.transaction{}
40
+ assert_nothing_raised{BtrieveSession.transaction{}}
41
+ end
42
+
43
+ should "allow an empty transaction to go through, with explicit boundaries" do
44
+ assert_nothing_raised do
45
+ BtrieveSession.begin_transaction
46
+ BtrieveSession.end_transaction
47
+ end
48
+ end
49
+
50
+ should "allow a transaction to be aborted" do
51
+ assert_nothing_raised do
52
+ BtrieveSession.begin_transaction
53
+ BtrieveSession.abort_transaction
54
+ end
55
+ assert_raise(RuntimeError){BtrieveSession.end_transaction}
29
56
  end
30
57
 
31
58
  context "and an instance of BtrieveTable is created, it" do
32
59
  setup do
33
60
  @table=BtrieveTable.new(:course)
61
+ @table.primary_key = [:name]
34
62
  end
35
63
 
36
64
  teardown do
@@ -38,17 +66,18 @@ class TestBeezwax < Test::Unit::TestCase
38
66
  end
39
67
 
40
68
  should "be possible to open it for operations, then close it" do
41
- assert_nothing_raised(RuntimeError){@table.open}
42
- assert_nothing_raised(RuntimeError){@table.close}
69
+ assert_nothing_raised{ @table.open }
70
+ assert_nothing_raised{ @table.close }
43
71
  end
44
72
 
45
73
  should "be possible to have the file automatically closed when a block is passed when opening a table" do
46
- @table.open{}
47
- assert_raise(RuntimeError){@table.close}
74
+ assert_nothing_raised{ @table.open{} }
75
+ assert_raise(RuntimeError){ @table.close }
48
76
  end
49
77
 
50
78
  should "return a version number for that table" do
51
- table_version=@table.version
79
+ table_version=nil
80
+ assert_nothing_raised{ table_version=@table.version }
52
81
  assert_not_nil table_version
53
82
  assert_equal 32, table_version.size
54
83
  end
@@ -65,16 +94,13 @@ class TestBeezwax < Test::Unit::TestCase
65
94
 
66
95
  should "be possible to find a particular record in a unique index" do
67
96
  name = 'PHI 101'
68
- record=@table.find({:name=>name}, 0).first
97
+ record = nil
98
+ record_name = nil
99
+ assert_nothing_raised{record=@table.find({:name=>name}, 0).first}
69
100
  assert_not_nil record
70
- assert_equal record[:name], name
71
- end
72
-
73
- should "be possible to fetch a particular record and all its columns should be properly set" do
74
- name = 'PHI 101'
75
- record=@table.find({:name=>name}, 0).first
76
- expected_values={:name=>name, :description=>'Introduction to Ethics', :credit_hours=>3, :dept_name=>'Philosophy'}
77
- assert_equal expected_values, record.values
101
+ assert_nothing_raised{record_name=record[:name]}
102
+ assert_not_nil record_name
103
+ assert_equal record_name, name
78
104
  end
79
105
 
80
106
  should "be possible to search for a unique key that doesn't exist and not cause an exception" do
@@ -96,9 +122,7 @@ class TestBeezwax < Test::Unit::TestCase
96
122
  should "support finding records and looping over them if a block is passed in" do
97
123
  dept_name = 'Mathematics'
98
124
  counter = 0
99
- @table.find({:dept_name=>dept_name}, 1) do
100
- counter += 1
101
- end
125
+ assert_nothing_raised{@table.find({:dept_name=>dept_name}, 1){counter += 1}}
102
126
  assert_equal 9, counter
103
127
  end
104
128
 
@@ -108,9 +132,125 @@ class TestBeezwax < Test::Unit::TestCase
108
132
  assert_not_nil records
109
133
  assert records.empty?, "records is NOT empty"
110
134
  end
111
-
112
-
113
- end
135
+
136
+ context "and an instance of BtrieveRecord is fetched, it" do
137
+ setup do
138
+ @name = 'PHI 101'
139
+ @record=@table.find({:name=>@name}, 0).first
140
+ end
141
+
142
+ teardown do
143
+ @record=nil
144
+ end
145
+
146
+ should "be possible to get the record's primary key" do
147
+ expected_value = {:name=>@name}
148
+ pk = nil
149
+ assert_nothing_raised{ pk = @record.primary_key }
150
+ assert_not_nil pk
151
+ assert_equal expected_value, pk
152
+ end
153
+
154
+ should "be possible retrieve all its columns" do
155
+ expected_values={:name=>@name, :description=>'Introduction to Ethics', :credit_hours=>3, :dept_name=>'Philosophy'}
156
+ values = nil
157
+ assert_nothing_raised{ values = @record.values }
158
+ assert_not_nil values
159
+ assert_equal expected_values, values
160
+ end
161
+
162
+ should "be possible retrieve a subset of its columns" do
163
+ expected_values={:description=>'Introduction to Ethics', :credit_hours=>3}
164
+ values = nil
165
+ assert_nothing_raised{ values = @record.values([:description, :credit_hours]) }
166
+ assert_not_nil values
167
+ assert_equal expected_values, values
168
+ end
169
+ end
170
+
171
+ context "for a BtrieveRecord to be created, it" do
172
+ setup do
173
+ @values={:name=>'FRA 303', :description=>'Francais Avance', :credit_hours=>3, :dept_name=>"Latin".force_encoding("ASCII-8BIT")}
174
+ @rb_values={:name=>'FRE 303', :description=>'Frenceis Evence', :credit_hours=>3, :dept_name=>'Latin'}
175
+ end
176
+
177
+ teardown do
178
+ # meh
179
+ end
180
+
181
+ should "be able to create a persistent entry in a transaction, but then rollback and not commit anything" do
182
+ BtrieveSession.begin_transaction
183
+ assert_nothing_raised do
184
+ @table.open do
185
+ record=BtrieveRecord.new(@table)
186
+ record.values=@rb_values
187
+ record.insert
188
+ end
189
+ end
190
+ new_record = nil
191
+ new_values = nil
192
+ assert_nothing_raised{ new_record = @table.find({:name=>'FRE 303'}, 0).first }
193
+ assert_not_nil new_record
194
+ assert_nothing_raised { new_values = new_record.values }
195
+ assert_not_nil new_values
196
+ assert !new_values.empty?
197
+ assert_equal @rb_values, new_values
198
+ BtrieveSession.abort_transaction
199
+ post_rollback=nil
200
+ assert_nothing_raised{ post_rollback = @table.find({:name=>'FRE 303'}, 0) }
201
+ assert_not_nil post_rollback
202
+ assert post_rollback.empty?
203
+ end
204
+
205
+ should "be to persist a record, find it, change it and then delete it" do
206
+ BtrieveSession.transaction do
207
+ assert_nothing_raised do
208
+ @table.open do
209
+ record=BtrieveRecord.new(@table)
210
+ record.values=@values
211
+ record.insert
212
+ end
213
+ end
214
+ end
215
+ new_record = nil
216
+ new_values = nil
217
+ assert_nothing_raised{ new_record = @table.find({:name=>'FRA 303'}, 0).first }
218
+ assert_not_nil new_record
219
+ assert_nothing_raised { new_values = new_record.values }
220
+ assert_not_nil new_values
221
+ assert !new_values.empty?
222
+ assert_equal @values, new_values
223
+ updated_dept = 'D.M.V.'
224
+ BtrieveSession.transaction do
225
+ assert_nothing_raised do
226
+ @table.open do
227
+ buffer_length=new_record.data_buffer.size
228
+ new_record[:dept_name]=updated_dept
229
+ assert_equal buffer_length, new_record.data_buffer.size
230
+ new_record.update
231
+ assert_equal buffer_length, new_record.data_buffer.size
232
+ str = nil
233
+ assert_nothing_raised{ str = new_record.to_s }
234
+ assert str.size > 0
235
+ end
236
+ end
237
+ end
238
+ assert_nothing_raised{ new_record = @table.find({:name=>'FRA 303'}, 0).first }
239
+ assert_not_nil new_record
240
+ assert_nothing_raised { new_values = new_record.values }
241
+ assert_not_nil new_values
242
+ assert !new_values.empty?
243
+ assert_equal updated_dept, new_values[:dept_name]
244
+ BtrieveSession.transaction do
245
+ assert_nothing_raised do
246
+ @table.open{new_record.delete}
247
+ end
248
+ end
249
+ assert_nothing_raised{ new_record = @table.find({:name=>'FRA 303'}, 0).first }
250
+ assert_nil new_record
251
+ end
252
+ end
253
+ end
114
254
  end
115
255
 
116
256
  context "A destroyed BtrieveSession" do
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 6
8
- - 0
9
- version: 0.6.0
8
+ - 1
9
+ version: 0.6.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Patrick Lardin
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-19 00:00:00 -08:00
17
+ date: 2010-12-20 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency