dbf 0.4.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/Manifest.txt +28 -0
- data/README.txt +32 -20
- data/Rakefile +2 -1
- data/{test → benchmarks}/performance.rb +0 -0
- data/benchmarks/seek_benchmark.rb +18 -0
- data/lib/dbf.rb +1 -1
- data/lib/dbf/reader.rb +189 -61
- data/lib/dbf/record.rb +1 -1
- data/spec/field_spec.rb +34 -0
- data/spec/fixtures/dbase_iii_memo_schema.rb +19 -0
- data/spec/reader_spec.rb +142 -0
- data/spec/record_spec.rb +9 -0
- data/spec/spec_helper.rb +8 -0
- data/test/databases/dbase_iii_memo.dbf +0 -0
- data/test/databases/dbase_iii_memo.dbt +0 -0
- data/test/dbase_iii_memo_read_test.rb +27 -0
- data/test/visual_foxpro_read_test.rb +27 -0
- metadata +20 -8
- data/LICENSE.txt +0 -22
data/History.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
== 0.5.0 / 2007-05-25
|
2
|
+
|
3
|
+
* New find method
|
4
|
+
* Full compatibility with the two flavors of memo file
|
5
|
+
* Two modes of operation:
|
6
|
+
* In memory (default): All records are loaded into memory on the first
|
7
|
+
request. Records are retrieved from memory for all subsequent requests.
|
8
|
+
* File I/O: All records are retrieved from disk on every request
|
9
|
+
* Improved documentation and more usage examples
|
data/Manifest.txt
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
benchmarks/performance.rb
|
6
|
+
benchmarks/seek_benchmark.rb
|
7
|
+
lib/dbf.rb
|
8
|
+
lib/dbf/field.rb
|
9
|
+
lib/dbf/globals.rb
|
10
|
+
lib/dbf/reader.rb
|
11
|
+
lib/dbf/record.rb
|
12
|
+
spec/field_spec.rb
|
13
|
+
spec/fixtures/dbase_iii_memo_schema.rb
|
14
|
+
spec/reader_spec.rb
|
15
|
+
spec/record_spec.rb
|
16
|
+
spec/spec_helper.rb
|
17
|
+
test/common.rb
|
18
|
+
test/databases/dbase_iii.dbf
|
19
|
+
test/databases/dbase_iii_memo.dbf
|
20
|
+
test/databases/dbase_iii_memo.dbt
|
21
|
+
test/databases/foxpro.dbf
|
22
|
+
test/databases/foxpro.fpt
|
23
|
+
test/databases/visual_foxpro.dbf
|
24
|
+
test/databases/visual_foxpro.fpt
|
25
|
+
test/dbase_iii_memo_read_test.rb
|
26
|
+
test/dbase_iii_read_test.rb
|
27
|
+
test/foxpro_read_test.rb
|
28
|
+
test/visual_foxpro_read_test.rb
|
data/README.txt
CHANGED
@@ -2,47 +2,59 @@
|
|
2
2
|
|
3
3
|
DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies.
|
4
4
|
|
5
|
+
Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org, www.infused.org>
|
6
|
+
|
7
|
+
* Official project page: http://rubyforge.org/projects/dbf
|
8
|
+
* API Documentation: http://dbf.rubyforge.org/docs
|
9
|
+
* To report bugs: http://www.rubyforge.org/tracker/?group_id=2009
|
10
|
+
* Questions: Email keithm@infused.org and put DBF somewhere in the subject
|
11
|
+
line
|
12
|
+
|
5
13
|
== Features
|
6
14
|
|
7
15
|
* No external dependencies
|
8
|
-
*
|
16
|
+
* Fields are type cast to the appropriate Ruby types
|
9
17
|
* Date/Time fields are returned as either a Time or Date object. Date
|
10
|
-
will only be used if the date is
|
18
|
+
will only be used if the date is out of range for Ruby's built in Time
|
19
|
+
class.
|
20
|
+
* Ability to dump the database schema in the portable ActiveRecord::Schema
|
21
|
+
format.
|
11
22
|
|
12
23
|
== Installation
|
13
|
-
|
24
|
+
|
14
25
|
gem install dbf
|
15
26
|
|
16
|
-
== Usage
|
27
|
+
== Basic Usage
|
28
|
+
|
29
|
+
require 'rubygems'
|
30
|
+
require 'dbf'
|
17
31
|
|
18
32
|
reader = DBF::Reader.new("old_data.dbf")
|
19
33
|
|
34
|
+
# Print the 'name' field from record number 4
|
35
|
+
puts reader.record(4)['name']
|
36
|
+
|
37
|
+
# Print the 'name' and 'address' fields from each record
|
20
38
|
reader.records.each do |record|
|
21
39
|
puts record['name']
|
22
40
|
puts record['email']
|
23
41
|
end
|
24
|
-
|
25
|
-
puts reader.records[4]['name']
|
26
|
-
puts reader.record(4)['name']
|
27
|
-
|
28
|
-
=== A note on record vs. records
|
29
|
-
|
30
|
-
DBF::Reader#records is an in-memory array of all rows in the database. All
|
31
|
-
rows are loaded the first time that the method is called. Subsequent calls
|
32
|
-
retrieve the row from memory.
|
33
|
-
|
34
|
-
DBF::Reader#record retrieves the requested row from the database each time
|
35
|
-
it is called.
|
36
|
-
|
37
|
-
Using records is probably faster most of the time. Record is more appropriate
|
38
|
-
for very large databases where you don't want the whole db loaded into memory.
|
39
42
|
|
43
|
+
# Find records
|
44
|
+
reader.find :all, :first_name => 'Keith'
|
45
|
+
reader.find :all, :first_name => 'Keith', :last_name => 'Morrison'
|
46
|
+
reader.find :first, :first_name => 'Keith'
|
47
|
+
reader.find(10)
|
48
|
+
|
40
49
|
== Limitations and known bugs
|
41
50
|
|
42
|
-
* DBF is read-only
|
51
|
+
* DBF is read-only at the moment
|
52
|
+
* Index files are not utilized
|
43
53
|
|
44
54
|
== License
|
45
55
|
|
56
|
+
(The MIT Licence)
|
57
|
+
|
46
58
|
Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
|
47
59
|
|
48
60
|
Permission is hereby granted, free of charge, to any person
|
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'hoe'
|
|
2
2
|
require 'spec/rake/spectask'
|
3
3
|
|
4
4
|
PKG_NAME = "dbf"
|
5
|
-
PKG_VERSION = "0.
|
5
|
+
PKG_VERSION = "0.5.0"
|
6
6
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
7
7
|
|
8
8
|
Hoe.new PKG_NAME, PKG_VERSION do |p|
|
@@ -10,6 +10,7 @@ Hoe.new PKG_NAME, PKG_VERSION do |p|
|
|
10
10
|
p.author = "Keith Morrison"
|
11
11
|
p.email = "keithm@infused.org"
|
12
12
|
p.summary = "A small fast library for reading dBase, xBase, Clipper and FoxPro database files."
|
13
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
13
14
|
p.url = "http://dbf.rubyforge.org"
|
14
15
|
p.need_tar = true
|
15
16
|
p.need_zip = true
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'benchmark'
|
3
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
4
|
+
require 'dbf'
|
5
|
+
|
6
|
+
puts
|
7
|
+
puts "Runs 5000 random row seeks first using the I/O based record(n) method and then using"
|
8
|
+
puts "using the array of records."
|
9
|
+
puts
|
10
|
+
|
11
|
+
iterations = 5000
|
12
|
+
Benchmark.bm(20) do |x|
|
13
|
+
@dbf = DBF::Reader.new(File.join(File.dirname(__FILE__), '..', 'test', 'databases', 'foxpro.dbf'))
|
14
|
+
max = @dbf.record_count + 1
|
15
|
+
|
16
|
+
x.report("I/O based record(n)") { iterations.times { @dbf.record(rand(max)) } }
|
17
|
+
x.report("array of records[n]") { iterations.times { @dbf.records[rand(max)] } }
|
18
|
+
end
|
data/lib/dbf.rb
CHANGED
data/lib/dbf/reader.rb
CHANGED
@@ -1,119 +1,247 @@
|
|
1
1
|
module DBF
|
2
2
|
class Reader
|
3
|
+
# The total number of fields (columns)
|
3
4
|
attr_reader :field_count
|
5
|
+
# An array of DBF::Field records
|
4
6
|
attr_reader :fields
|
7
|
+
# The total number of records. This number includes any deleted records.
|
5
8
|
attr_reader :record_count
|
9
|
+
# Internal dBase version number
|
6
10
|
attr_reader :version
|
11
|
+
# Last updated datetime
|
7
12
|
attr_reader :last_updated
|
13
|
+
# Either :fpt or :dpt
|
8
14
|
attr_reader :memo_file_format
|
15
|
+
# The block size for memo records
|
9
16
|
attr_reader :memo_block_size
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
# Initialize a new DBF::Reader.
|
19
|
+
# Example:
|
20
|
+
# reader = DBF::Reader.new 'data.dbf'
|
21
|
+
def initialize(filename)
|
22
|
+
@in_memory = true
|
23
|
+
@data_file = File.open(filename, 'rb')
|
24
|
+
@memo_file = open_memo(filename)
|
14
25
|
reload!
|
15
26
|
end
|
16
27
|
|
28
|
+
# Reloads the database and memo files
|
17
29
|
def reload!
|
30
|
+
@records = nil
|
18
31
|
get_header_info
|
19
32
|
get_memo_header_info if @memo_file
|
20
33
|
get_field_descriptors
|
21
34
|
end
|
22
35
|
|
36
|
+
# Returns true if there is a corresponding memo file
|
23
37
|
def has_memo_file?
|
24
38
|
@memo_file ? true : false
|
25
39
|
end
|
26
40
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if File.exists?(filename)
|
31
|
-
@memo_file_format = extension.downcase.to_sym
|
32
|
-
return File.open(filename, 'rb')
|
33
|
-
end
|
34
|
-
end
|
35
|
-
nil
|
41
|
+
# If true, DBF::Reader will load all records into memory. If false, records are retrieved using file I/O.
|
42
|
+
def in_memory?
|
43
|
+
@in_memory
|
36
44
|
end
|
37
45
|
|
46
|
+
# Tells DBF::Reader whether to load all records into memory. Defaults to true.
|
47
|
+
# You may need to set this to false if the database is very large in order to reduce memory usage.
|
48
|
+
def in_memory=(boolean)
|
49
|
+
@in_memory = boolean
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns an instance of DBF::Field for <b>field_name</b>. <b>field_name</b>
|
53
|
+
# can be a symbol or a string.
|
38
54
|
def field(field_name)
|
39
55
|
@fields.detect {|f| f.name == field_name.to_s}
|
40
56
|
end
|
41
57
|
|
42
58
|
# An array of all the records contained in the database file
|
43
59
|
def records
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
else
|
49
|
-
seek_to_record(i + 1)
|
50
|
-
nil
|
51
|
-
end
|
60
|
+
if in_memory?
|
61
|
+
@records ||= get_all_records_from_file
|
62
|
+
else
|
63
|
+
get_all_records_from_file
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
alias_method :rows, :records
|
56
68
|
|
57
|
-
# Returns the record at <
|
58
|
-
# physical database file. See the documentation for the records method for
|
59
|
-
# information on how these two methods differ.
|
69
|
+
# Returns the record at <tt>index</tt>.
|
60
70
|
def record(index)
|
61
|
-
|
62
|
-
|
71
|
+
if in_memory?
|
72
|
+
records[index]
|
73
|
+
else
|
74
|
+
get_record_from_file(index)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Find records. Examples:
|
79
|
+
# reader = DBF::Reader.new 'mydata.dbf'
|
80
|
+
#
|
81
|
+
# # Find record number 5
|
82
|
+
# reader.find(5)
|
83
|
+
#
|
84
|
+
# # Find all records for Keith Morrison
|
85
|
+
# reader.find :all, :first_name => "Keith", :last_name => "Morrison"
|
86
|
+
#
|
87
|
+
# # Find first record
|
88
|
+
# reader.find :first, :first_name => "Keith"
|
89
|
+
#
|
90
|
+
# The <b>command</b> can be an id, :all, or :first.
|
91
|
+
# <b>options</b> is optional and, if specified, should be a hash where the keys correspond
|
92
|
+
# to field names in the database. The values will be matched exactly with the value
|
93
|
+
# in the database. If you specify more than one key, all values must match in order
|
94
|
+
# for the record to be returned. The equivalent SQL would be "WHERE key1 = 'value1'
|
95
|
+
# AND key2 = 'value2'".
|
96
|
+
def find(command, options = {})
|
97
|
+
case command
|
98
|
+
when Fixnum
|
99
|
+
if !options.empty?
|
100
|
+
raise ArgumentError, "options are not allowed when command is a record number"
|
101
|
+
end
|
102
|
+
record(command)
|
103
|
+
when :all
|
104
|
+
records.select do |record|
|
105
|
+
options.map {|key, value| record[key.to_s] == value}.all?
|
106
|
+
end
|
107
|
+
when :first
|
108
|
+
return records.first if options.empty?
|
109
|
+
records.detect do |record|
|
110
|
+
options.map {|key, value| record[key.to_s] == value}.all?
|
111
|
+
end
|
112
|
+
end
|
63
113
|
end
|
64
114
|
|
65
115
|
alias_method :row, :record
|
66
116
|
|
117
|
+
# Returns a description of the current database file.
|
67
118
|
def version_description
|
68
119
|
VERSION_DESCRIPTIONS[version]
|
69
120
|
end
|
70
121
|
|
122
|
+
# Returns a database schema in the portable ActiveRecord::Schema format.
|
123
|
+
#
|
124
|
+
# xBase data types are converted to generic types as follows:
|
125
|
+
# - Number fields are converted to :integer if there are no decimals, otherwise
|
126
|
+
# they are converted to :float
|
127
|
+
# - Date fields are converted to :datetime
|
128
|
+
# - Logical fields are converted to :boolean
|
129
|
+
# - Memo fields are converted to :text
|
130
|
+
# - Character fields are converted to :string and the :limit option is set
|
131
|
+
# to the length of the character field
|
132
|
+
#
|
133
|
+
# Example:
|
134
|
+
# create_table "mydata" do |t|
|
135
|
+
# t.column :name, :string, :limit => 30
|
136
|
+
# t.column :last_update, :datetime
|
137
|
+
# t.column :is_active, :boolean
|
138
|
+
# t.column :age, :integer
|
139
|
+
# t.column :notes, :text
|
140
|
+
# end
|
141
|
+
def schema(path = nil)
|
142
|
+
s = "ActiveRecord::Schema.define do\n"
|
143
|
+
s << " create_table \"#{File.basename(@data_file.path, ".*")}\" do |t|\n"
|
144
|
+
fields.each do |field|
|
145
|
+
s << " t.column \"#{field.name}\""
|
146
|
+
case field.type
|
147
|
+
when "N" # number
|
148
|
+
if field.decimal > 0
|
149
|
+
s << ", :float"
|
150
|
+
else
|
151
|
+
s << ", :integer"
|
152
|
+
end
|
153
|
+
when "D" # date
|
154
|
+
s << ", :datetime"
|
155
|
+
when "L" # boolean
|
156
|
+
s << ", :boolean"
|
157
|
+
when "M" # memo
|
158
|
+
s << ", :text"
|
159
|
+
else
|
160
|
+
s << ", :string, :limit => #{field.length}"
|
161
|
+
end
|
162
|
+
s << "\n"
|
163
|
+
end
|
164
|
+
s << " end\nend"
|
165
|
+
|
166
|
+
if path
|
167
|
+
return File.open(path, 'w') {|f| f.puts(s)}
|
168
|
+
else
|
169
|
+
return s
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
71
173
|
private
|
72
174
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
175
|
+
def open_memo(file)
|
176
|
+
%w(fpt FPT dbt DBT).each do |extension|
|
177
|
+
filename = file.sub(/#{File.extname(file)[1..-1]}$/, extension)
|
178
|
+
if File.exists?(filename)
|
179
|
+
@memo_file_format = extension.downcase.to_sym
|
180
|
+
return File.open(filename, 'rb')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
nil
|
184
|
+
end
|
80
185
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
186
|
+
# Returns false if the record has been marked as deleted, otherwise it returns true. When dBase records are deleted a
|
187
|
+
# flag is set, marking the record as deleted. The record will not be fully removed until the database has been compacted.
|
188
|
+
def active_record?
|
189
|
+
@data_file.read(1).unpack('H2').to_s == '20'
|
190
|
+
rescue
|
191
|
+
false
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_header_info
|
195
|
+
@data_file.rewind
|
196
|
+
@version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
|
197
|
+
@field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
|
198
|
+
end
|
86
199
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
200
|
+
def get_field_descriptors
|
201
|
+
@fields = []
|
202
|
+
@field_count.times do
|
203
|
+
name, type, length, decimal = @data_file.read(32).unpack('a10xax4CC')
|
204
|
+
if length > 0 && !name.strip.empty?
|
205
|
+
@fields << Field.new(name, type, length, decimal)
|
206
|
+
end
|
93
207
|
end
|
208
|
+
# adjust field count
|
209
|
+
@field_count = @fields.size
|
210
|
+
@fields
|
94
211
|
end
|
95
|
-
# adjust field count
|
96
|
-
@field_count = @fields.size
|
97
|
-
@fields
|
98
|
-
end
|
99
212
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
213
|
+
def get_memo_header_info
|
214
|
+
@memo_file.rewind
|
215
|
+
if @memo_file_format == :fpt
|
216
|
+
@memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
|
217
|
+
else
|
218
|
+
@memo_block_size = 512
|
219
|
+
@memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
|
220
|
+
end
|
107
221
|
end
|
108
|
-
end
|
109
222
|
|
110
|
-
|
111
|
-
|
112
|
-
|
223
|
+
def seek(offset)
|
224
|
+
@data_file.seek(@header_length + offset)
|
225
|
+
end
|
113
226
|
|
114
|
-
|
115
|
-
|
116
|
-
|
227
|
+
def seek_to_record(index)
|
228
|
+
seek(index * @record_length)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns the record at <tt>index</tt> by seeking to the record in the
|
232
|
+
# physical database file. See the documentation for the records method for
|
233
|
+
# information on how these two methods differ.
|
234
|
+
def get_record_from_file(index)
|
235
|
+
seek_to_record(index)
|
236
|
+
active_record? ? Record.new(self, @data_file, @memo_file) : nil
|
237
|
+
end
|
238
|
+
|
239
|
+
def get_all_records_from_file
|
240
|
+
seek_to_record(0)
|
241
|
+
Array.new(@record_count) do |i|
|
242
|
+
active_record? ? DBF::Record.new(self, @data_file, @memo_file) : nil
|
243
|
+
end
|
244
|
+
end
|
117
245
|
|
118
246
|
end
|
119
247
|
|
data/lib/dbf/record.rb
CHANGED
data/spec/field_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe DBF::Field, "when initialized" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@field = DBF::Field.new "FieldName", "N", 1, 0
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should set the 'name' accessor" do
|
10
|
+
@field.name.should == "FieldName"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set the 'type' accessor" do
|
14
|
+
@field.type.should == "N"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set the 'length' accessor" do
|
18
|
+
@field.length.should == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the 'decimal' accessor" do
|
22
|
+
@field.decimal.should == 0
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should raise an error if length is greater than 0" do
|
26
|
+
lambda { field = DBF::Field.new "FieldName", "N", -1, 0 }.should raise_error(DBF::FieldLengthError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should strip null characters from the name" do
|
30
|
+
field = DBF::Field.new "Field\0Name\0", "N", 1, 0
|
31
|
+
field.name.should == "FieldName"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table "dbase_iii_memo" do |t|
|
3
|
+
t.column "ID", :integer
|
4
|
+
t.column "CATCOUNT", :integer
|
5
|
+
t.column "AGRPCOUNT", :integer
|
6
|
+
t.column "PGRPCOUNT", :integer
|
7
|
+
t.column "ORDER", :integer
|
8
|
+
t.column "CODE", :string, :limit => 50
|
9
|
+
t.column "NAME", :string, :limit => 100
|
10
|
+
t.column "THUMBNAIL", :string, :limit => 254
|
11
|
+
t.column "IMAGE", :string, :limit => 254
|
12
|
+
t.column "PRICE", :float
|
13
|
+
t.column "COST", :float
|
14
|
+
t.column "DESC", :text
|
15
|
+
t.column "WEIGHT", :float
|
16
|
+
t.column "TAXABLE", :boolean
|
17
|
+
t.column "ACTIVE", :boolean
|
18
|
+
end
|
19
|
+
end
|
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe DBF::Reader, "when initialized" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should load the data file" do
|
10
|
+
@reader.instance_eval("@data_file").should be_kind_of(File)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should locate load the memo file" do
|
14
|
+
@reader.has_memo_file?.should be_true
|
15
|
+
@reader.instance_eval("@memo_file").should be_kind_of(File)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should determine the memo file format" do
|
19
|
+
@reader.memo_file_format.should == :dbt
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should determine the memo block size" do
|
23
|
+
@reader.memo_block_size.should == 512
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should default to loading all records into memory" do
|
27
|
+
@reader.in_memory?.should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should determine the number of fields in each record" do
|
31
|
+
@reader.fields.size.should == 15
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should determine the number of records in the database" do
|
35
|
+
@reader.record_count.should == 67
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should determine the database version" do
|
39
|
+
@reader.version.should == "83"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe DBF::Reader, "when the in_memory flag is true" do
|
45
|
+
|
46
|
+
before(:each) do
|
47
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should build the records array from disk only on the first request" do
|
51
|
+
@reader.expects(:get_all_records_from_file).at_most_once.returns([])
|
52
|
+
3.times { @reader.records }
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should read from the records array when using the record() method" do
|
56
|
+
@reader.expects(:get_all_records_from_file).at_most_once.returns([])
|
57
|
+
@reader.expects(:get_record_from_file).never
|
58
|
+
@reader.expects(:records).times(2).returns([])
|
59
|
+
@reader.record(1)
|
60
|
+
@reader.record(10)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe DBF::Reader, "when the in_memory flag is false" do
|
66
|
+
|
67
|
+
before(:each) do
|
68
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should read the records from disk on every request" do
|
72
|
+
@reader.in_memory = false
|
73
|
+
@reader.expects(:get_all_records_from_file).times(3).returns([])
|
74
|
+
3.times { @reader.records }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe DBF::Reader, "schema" do
|
79
|
+
|
80
|
+
it "should match test schema " do
|
81
|
+
reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
82
|
+
control_schema = File.read(File.dirname(__FILE__) + '/fixtures/dbase_iii_memo_schema.rb')
|
83
|
+
|
84
|
+
reader.schema.should == control_schema
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe DBF::Reader, "find(index)" do
|
90
|
+
|
91
|
+
before(:all) do
|
92
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should return the correct record" do
|
96
|
+
@reader.find(5).should == @reader.record(5)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should raise an error if options are not empty" do
|
100
|
+
lambda { @reader.find(5, :name => 'test') }.should raise_error(ArgumentError)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
describe DBF::Reader, "find(:all)" do
|
106
|
+
|
107
|
+
before(:all) do
|
108
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return all records if options are empty" do
|
112
|
+
@reader.find(:all).should == @reader.records
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should return matching records when used with options" do
|
116
|
+
@reader.find(:all, "WEIGHT" => 0.0).should == @reader.records.select {|r| r["WEIGHT"] == 0.0}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "with multiple options should search for all search terms as if using AND" do
|
120
|
+
@reader.find(:all, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should == []
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe DBF::Reader, "find(:first)" do
|
125
|
+
|
126
|
+
before(:all) do
|
127
|
+
@reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should return the first record if options are empty" do
|
131
|
+
@reader.find(:first).should == @reader.records.first
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should return the first matching record when used with options" do
|
135
|
+
@reader.find(:first, "CODE" => "C").should == @reader.record(5)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "with multiple options should search for all search terms as if using AND" do
|
139
|
+
@reader.find(:first, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should be_nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
data/spec/record_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
2
|
+
require 'test/unit'
|
3
|
+
require 'dbf'
|
4
|
+
require 'common'
|
5
|
+
|
6
|
+
class DBaseIIIWithMemoReadTest < Test::Unit::TestCase
|
7
|
+
include CommonTests::Read
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@controls = {
|
11
|
+
:version => "83",
|
12
|
+
:has_memo_file => true,
|
13
|
+
:memo_file_format => :dbt,
|
14
|
+
:field_count => 15,
|
15
|
+
:record_count => 67,
|
16
|
+
:record_length => 805,
|
17
|
+
:testable_character_field_names => ["CODE"],
|
18
|
+
:testable_date_field_names => [],
|
19
|
+
:testable_integer_field_names => ["AGRPCOUNT"],
|
20
|
+
:testable_float_field_names => ["PRICE"],
|
21
|
+
:testable_logical_field_names => ["TAXABLE"],
|
22
|
+
:testable_memo_field_names => ["DESC"]
|
23
|
+
}
|
24
|
+
@dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'dbase_iii_memo.dbf'))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
2
|
+
require 'test/unit'
|
3
|
+
require 'dbf'
|
4
|
+
require 'common'
|
5
|
+
|
6
|
+
class VisualFoxproReadTest < Test::Unit::TestCase
|
7
|
+
include CommonTests::Read
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@controls = {
|
11
|
+
:version => "30",
|
12
|
+
:has_memo_file => true,
|
13
|
+
:memo_file_format => :fpt,
|
14
|
+
:field_count => 145,
|
15
|
+
:record_count => 34,
|
16
|
+
:record_length => 3907,
|
17
|
+
:testable_character_field_names => [],
|
18
|
+
:testable_date_field_names => [],
|
19
|
+
:testable_integer_field_names => ["IMAGENO"],
|
20
|
+
:testable_float_field_names => [],
|
21
|
+
:testable_logical_field_names => [],
|
22
|
+
:testable_memo_field_names => ["CREDIT"]
|
23
|
+
}
|
24
|
+
@dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'visual_foxpro.dbf'))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: dbf
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-05-
|
6
|
+
version: 0.5.0
|
7
|
+
date: 2007-05-26 00:00:00 -07:00
|
8
8
|
summary: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,31 +29,43 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- Keith Morrison
|
31
31
|
files:
|
32
|
-
-
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
33
34
|
- README.txt
|
34
|
-
-
|
35
|
+
- Rakefile
|
36
|
+
- benchmarks/performance.rb
|
37
|
+
- benchmarks/seek_benchmark.rb
|
35
38
|
- lib/dbf.rb
|
36
39
|
- lib/dbf/field.rb
|
37
40
|
- lib/dbf/globals.rb
|
38
41
|
- lib/dbf/reader.rb
|
39
42
|
- lib/dbf/record.rb
|
43
|
+
- spec/field_spec.rb
|
44
|
+
- spec/fixtures/dbase_iii_memo_schema.rb
|
45
|
+
- spec/reader_spec.rb
|
46
|
+
- spec/record_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
40
48
|
- test/common.rb
|
41
|
-
- test/dbase_iii_read_test.rb
|
42
|
-
- test/foxpro_read_test.rb
|
43
|
-
- test/performance.rb
|
44
49
|
- test/databases/dbase_iii.dbf
|
50
|
+
- test/databases/dbase_iii_memo.dbf
|
51
|
+
- test/databases/dbase_iii_memo.dbt
|
45
52
|
- test/databases/foxpro.dbf
|
46
53
|
- test/databases/foxpro.fpt
|
47
54
|
- test/databases/visual_foxpro.dbf
|
48
55
|
- test/databases/visual_foxpro.fpt
|
56
|
+
- test/dbase_iii_memo_read_test.rb
|
57
|
+
- test/dbase_iii_read_test.rb
|
58
|
+
- test/foxpro_read_test.rb
|
59
|
+
- test/visual_foxpro_read_test.rb
|
49
60
|
test_files: []
|
50
61
|
|
51
62
|
rdoc_options:
|
52
63
|
- --main
|
53
64
|
- README.txt
|
54
65
|
extra_rdoc_files:
|
66
|
+
- History.txt
|
67
|
+
- Manifest.txt
|
55
68
|
- README.txt
|
56
|
-
- LICENSE.txt
|
57
69
|
executables: []
|
58
70
|
|
59
71
|
extensions: []
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person
|
4
|
-
obtaining a copy of this software and associated documentation
|
5
|
-
files (the "Software"), to deal in the Software without
|
6
|
-
restriction, including without limitation the rights to use,
|
7
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
-
copies of the Software, and to permit persons to whom the
|
9
|
-
Software is furnished to do so, subject to the following
|
10
|
-
conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be
|
13
|
-
included in all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
-
OTHER DEALINGS IN THE SOFTWARE.
|