dbf 0.4.7 → 0.5.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/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.
|