og 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +2 -5
- data/README +2 -0
- data/doc/AUTHORS +4 -1
- data/doc/RELEASES +53 -0
- data/examples/run.rb +2 -2
- data/lib/{og/mixin → glue}/hierarchical.rb +19 -19
- data/lib/{og/mixin → glue}/optimistic_locking.rb +1 -1
- data/lib/glue/orderable.rb +235 -0
- data/lib/glue/revisable.rb +2 -0
- data/lib/glue/taggable.rb +176 -0
- data/lib/{og/mixin/taggable.rb → glue/taggable_old.rb} +6 -0
- data/lib/glue/timestamped.rb +37 -0
- data/lib/{og/mixin → glue}/tree.rb +3 -8
- data/lib/og.rb +21 -20
- data/lib/og/collection.rb +15 -1
- data/lib/og/entity.rb +256 -114
- data/lib/og/manager.rb +60 -27
- data/lib/og/{mixin/schema_inheritance_base.rb → markers.rb} +5 -2
- data/lib/og/relation.rb +70 -74
- data/lib/og/relation/belongs_to.rb +5 -3
- data/lib/og/relation/has_many.rb +1 -0
- data/lib/og/relation/joins_many.rb +5 -4
- data/lib/og/store.rb +25 -46
- data/lib/og/store/alpha/filesys.rb +1 -1
- data/lib/og/store/alpha/kirby.rb +30 -30
- data/lib/og/store/alpha/memory.rb +49 -49
- data/lib/og/store/alpha/sqlserver.rb +7 -7
- data/lib/og/store/kirby.rb +38 -38
- data/lib/og/store/mysql.rb +43 -43
- data/lib/og/store/psql.rb +222 -53
- data/lib/og/store/sql.rb +165 -105
- data/lib/og/store/sqlite.rb +29 -25
- data/lib/og/validation.rb +24 -14
- data/lib/{vendor → og/vendor}/README +0 -0
- data/lib/{vendor → og/vendor}/kbserver.rb +1 -1
- data/lib/{vendor → og/vendor}/kirbybase.rb +230 -79
- data/lib/{vendor → og/vendor}/mysql.rb +0 -0
- data/lib/{vendor → og/vendor}/mysql411.rb +0 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -1
- data/test/og/mixin/tc_orderable.rb +1 -1
- data/test/og/mixin/tc_taggable.rb +2 -2
- data/test/og/mixin/tc_timestamped.rb +2 -2
- data/test/og/tc_finder.rb +33 -0
- data/test/og/tc_inheritance.rb +2 -2
- data/test/og/tc_scoped.rb +45 -0
- data/test/og/tc_store.rb +1 -7
- metadata +21 -18
- data/lib/og/mixin/orderable.rb +0 -174
- data/lib/og/mixin/revisable.rb +0 -0
- data/lib/og/mixin/timestamped.rb +0 -24
data/lib/og/store/sqlite.rb
CHANGED
@@ -14,19 +14,19 @@ require 'og/store/sql'
|
|
14
14
|
|
15
15
|
class SQLite3::ResultSet
|
16
16
|
alias_method :blank?, :eof?
|
17
|
-
|
17
|
+
|
18
18
|
def each_row
|
19
19
|
each do |row|
|
20
20
|
yield(row, 0)
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def first_value
|
25
25
|
val = self.next[0]
|
26
26
|
close
|
27
27
|
return val
|
28
|
-
end
|
29
|
-
|
28
|
+
end
|
29
|
+
|
30
30
|
alias_method :fields, :columns
|
31
31
|
end
|
32
32
|
|
@@ -39,11 +39,12 @@ module Og
|
|
39
39
|
class SqliteStore < SqlStore
|
40
40
|
|
41
41
|
# Override if needed.
|
42
|
-
|
42
|
+
|
43
43
|
def self.db_filename(options)
|
44
|
+
options[:name] ||= 'data'
|
44
45
|
"#{options[:name]}.db"
|
45
46
|
end
|
46
|
-
|
47
|
+
|
47
48
|
def self.destroy(options)
|
48
49
|
begin
|
49
50
|
FileUtils.rm(db_filename(options))
|
@@ -53,6 +54,9 @@ class SqliteStore < SqlStore
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
57
|
+
# Initialize the Sqlite store.
|
58
|
+
# This store provides a default name.
|
59
|
+
|
56
60
|
def initialize(options)
|
57
61
|
super
|
58
62
|
@conn = SQLite3::Database.new(self.class.db_filename(options))
|
@@ -62,9 +66,9 @@ class SqliteStore < SqlStore
|
|
62
66
|
@conn.close
|
63
67
|
super
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
def enchant(klass, manager)
|
67
|
-
if klass.ann.
|
71
|
+
if klass.ann.self.primary_key.symbol == :oid
|
68
72
|
unless klass.properties.include? :oid
|
69
73
|
klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
|
70
74
|
end
|
@@ -91,12 +95,12 @@ class SqliteStore < SqlStore
|
|
91
95
|
@conn.transaction if @transaction_nesting < 1
|
92
96
|
@transaction_nesting += 1
|
93
97
|
end
|
94
|
-
|
98
|
+
|
95
99
|
def commit
|
96
100
|
@transaction_nesting -= 1
|
97
101
|
@conn.commit if @transaction_nesting < 1
|
98
102
|
end
|
99
|
-
|
103
|
+
|
100
104
|
def rollback
|
101
105
|
@transaction_nesting -= 1
|
102
106
|
@conn.rollback if @transaction_nesting < 1
|
@@ -117,18 +121,18 @@ private
|
|
117
121
|
fields = fields_for_class(klass)
|
118
122
|
|
119
123
|
sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
|
120
|
-
|
124
|
+
|
121
125
|
# Create table constraints.
|
122
|
-
|
123
|
-
if constraints = klass.ann.
|
126
|
+
|
127
|
+
if constraints = klass.ann.self[:sql_constraint]
|
124
128
|
sql << ", #{constraints.join(', ')}"
|
125
129
|
end
|
126
|
-
|
130
|
+
|
127
131
|
sql << ");"
|
128
|
-
|
132
|
+
|
129
133
|
# Create indices.
|
130
|
-
|
131
|
-
if indices = klass.ann.
|
134
|
+
|
135
|
+
if indices = klass.ann.self[:index]
|
132
136
|
for data in indices
|
133
137
|
idx, options = *data
|
134
138
|
idx = idx.to_s
|
@@ -150,11 +154,11 @@ private
|
|
150
154
|
raise
|
151
155
|
end
|
152
156
|
end
|
153
|
-
|
157
|
+
|
154
158
|
# Create join tables if needed. Join tables are used in
|
155
159
|
# 'many_to_many' relations.
|
156
|
-
|
157
|
-
|
160
|
+
|
161
|
+
if join_tables = klass.ann.self[:join_tables]
|
158
162
|
for info in join_tables
|
159
163
|
begin
|
160
164
|
create_join_table_sql(info).each do |sql|
|
@@ -176,13 +180,13 @@ private
|
|
176
180
|
def create_field_map(klass)
|
177
181
|
res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
|
178
182
|
map = {}
|
179
|
-
|
183
|
+
|
180
184
|
fields = res.columns
|
181
185
|
|
182
186
|
res.fields.size.times do |i|
|
183
187
|
map[fields[i].intern] = i
|
184
188
|
end
|
185
|
-
|
189
|
+
|
186
190
|
return map
|
187
191
|
ensure
|
188
192
|
res.close if res
|
@@ -197,15 +201,15 @@ private
|
|
197
201
|
props << Property.new(:symbol => :ogtype, :klass => String)
|
198
202
|
values << ", '#{klass}'"
|
199
203
|
end
|
200
|
-
|
204
|
+
|
201
205
|
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
|
202
206
|
|
203
207
|
klass.class_eval %{
|
204
208
|
def og_insert(store)
|
205
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
209
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
|
206
210
|
store.query("#{sql}").close
|
207
211
|
@#{pk} = store.last_insert_rowid
|
208
|
-
#{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
212
|
+
#{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
|
209
213
|
end
|
210
214
|
}
|
211
215
|
end
|
data/lib/og/validation.rb
CHANGED
@@ -14,34 +14,41 @@ module Validation
|
|
14
14
|
cattr_accessor :invalid_relation, 'Invalid relations'
|
15
15
|
end
|
16
16
|
|
17
|
-
module
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
# Validates that the given field(s) contain unique values.
|
20
|
+
# Ensures that if a record is found with a matching value,
|
21
|
+
# that it is the same record, allowing updates.
|
21
22
|
#
|
22
23
|
# The Og libraries are required for this methdod to
|
23
24
|
# work. You can override this method if you want to
|
24
25
|
# use another OR mapping library.
|
25
26
|
#
|
27
|
+
# === Example
|
28
|
+
#
|
29
|
+
# validate_unique :param, :msg => 'Value is already in use'
|
26
30
|
#--
|
27
31
|
# TODO: :unique should implicitly generate
|
28
32
|
# validate_unique.
|
29
33
|
#++
|
30
|
-
|
34
|
+
|
31
35
|
def validate_unique(*params)
|
32
|
-
c = {
|
33
|
-
:
|
34
|
-
:on => :save
|
36
|
+
c = {
|
37
|
+
:on => :save
|
35
38
|
}
|
36
39
|
c.update(params.pop) if params.last.is_a?(Hash)
|
37
40
|
|
38
|
-
for name in params
|
39
|
-
#
|
41
|
+
for name in params do
|
42
|
+
c[:msg] ||= "#{self.name} with this #{name} already exists"
|
43
|
+
|
40
44
|
code = %{
|
41
|
-
others = obj.class.find_by_#{name}(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
others = obj.class.send(:find_by_#{name}, obj.send(:#{name}))
|
46
|
+
|
47
|
+
unless others.empty?
|
48
|
+
if (others.size != 1) or (others[0].oid != obj.oid)
|
49
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
50
|
+
end
|
51
|
+
end
|
45
52
|
}
|
46
53
|
|
47
54
|
validations! << [code, c[:on]]
|
@@ -75,3 +82,6 @@ module Validation
|
|
75
82
|
end
|
76
83
|
|
77
84
|
end
|
85
|
+
|
86
|
+
# * George Moschovitis <gm@navel.gr>
|
87
|
+
# * Bryan Soto
|
File without changes
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'time'
|
2
3
|
require 'drb'
|
3
4
|
require 'csv'
|
4
5
|
require 'fileutils'
|
@@ -83,6 +84,31 @@ require 'yaml'
|
|
83
84
|
# :manager, :person (where :manager is the field name, and :person is the
|
84
85
|
# name of a table). See the docs for the specifics or ask Hal. :)
|
85
86
|
#
|
87
|
+
# 2005-11-13:: Version 2.4
|
88
|
+
# * Added a new column type: :Time. Thanks to George Moschovitis for coding
|
89
|
+
# this enhancement.
|
90
|
+
# * Added more functionality to Memo and Blob fields. They are no longer
|
91
|
+
# just read-only. You can now also write to them from KirbyBase. The
|
92
|
+
# interface for Memo and Blob fields has changed because of this.
|
93
|
+
# * Added the ability to specify, when you initialize a database connection,
|
94
|
+
# a base directory where memo/blob fields will be stored.
|
95
|
+
# * Changed the way indexes are handled by KBTable in client/server mode.
|
96
|
+
# Now, when KBTable grabs an index from KBEngine, it will hold onto it and
|
97
|
+
# re-use it unless it has been modified since the last time it grabbed it.
|
98
|
+
# This speeds up subsequent queries on the same index.
|
99
|
+
# * Removed the restriction that the child table had to exist before you
|
100
|
+
# could define a Link_many field in #create_table. I did this so that
|
101
|
+
# it would possible to now define many-to-many links. See the example in
|
102
|
+
# the distribution. This also goes for Lookup fields.
|
103
|
+
# * Added two sample scripts: kbserverctl.rb and kbserver_daemon.rb, that
|
104
|
+
# show how to set up a KirbyBase server process as a Windows Service.
|
105
|
+
# Thanks to Daniel Berger for his excellent package, win32-service.
|
106
|
+
# * Thouroughly revised the manual. I used the excellent text document
|
107
|
+
# formatter, AsciiDoc. Many thanks to Stuart Rackham for developing this
|
108
|
+
# great tool.
|
109
|
+
# * Fixed a bug in KBTable#clear that was causing the recno counter not to
|
110
|
+
# be reset. Thanks to basi for this.
|
111
|
+
#
|
86
112
|
#---------------------------------------------------------------------------
|
87
113
|
# KirbyBase
|
88
114
|
#---------------------------------------------------------------------------
|
@@ -91,7 +117,7 @@ class KirbyBase
|
|
91
117
|
|
92
118
|
attr_reader :engine
|
93
119
|
|
94
|
-
attr_accessor(:connect_type, :host, :port, :path, :ext)
|
120
|
+
attr_accessor(:connect_type, :host, :port, :path, :ext, :memo_blob_path)
|
95
121
|
|
96
122
|
#-----------------------------------------------------------------------
|
97
123
|
# initialize
|
@@ -107,14 +133,17 @@ class KirbyBase
|
|
107
133
|
# (Only valid if connect_type is :client.)
|
108
134
|
# *path*:: String specifying path to location of database tables.
|
109
135
|
# *ext*:: String specifying extension of table files.
|
136
|
+
# *memo_blob_path*:: String specifying path to location of memo/blob
|
137
|
+
# files.
|
110
138
|
#
|
111
139
|
def initialize(connect_type=:local, host=nil, port=nil, path='./',
|
112
|
-
ext='.tbl')
|
140
|
+
ext='.tbl', memo_blob_path='./')
|
113
141
|
@connect_type = connect_type
|
114
142
|
@host = host
|
115
143
|
@port = port
|
116
144
|
@path = path
|
117
145
|
@ext = ext
|
146
|
+
@memo_blob_path = memo_blob_path
|
118
147
|
|
119
148
|
# See if user specified any method arguments via a code block.
|
120
149
|
yield self if block_given?
|
@@ -122,7 +151,7 @@ class KirbyBase
|
|
122
151
|
# After the yield, make sure the user doesn't change any of these
|
123
152
|
# instance variables.
|
124
153
|
class << self
|
125
|
-
private(:connect_type=, :host=, :path=, :ext=)
|
154
|
+
private(:connect_type=, :host=, :path=, :ext=, :memo_blob_path=)
|
126
155
|
end
|
127
156
|
|
128
157
|
# Did user supply full and correct arguments to method?
|
@@ -134,6 +163,7 @@ class KirbyBase
|
|
134
163
|
@port.nil?
|
135
164
|
raise "Invalid path!" if @path.nil?
|
136
165
|
raise "Invalid extension!" if @ext.nil?
|
166
|
+
raise "Invalid memo/blob path!" if @memo_blob_path.nil?
|
137
167
|
|
138
168
|
@table_hash = {}
|
139
169
|
|
@@ -144,6 +174,7 @@ class KirbyBase
|
|
144
174
|
@engine = @server.engine
|
145
175
|
@path = @server.path
|
146
176
|
@ext = @server.ext
|
177
|
+
@memo_blob_path = @server.memo_blob_path
|
147
178
|
else
|
148
179
|
@engine = KBEngine.create_called_from_database_instance(self)
|
149
180
|
end
|
@@ -230,8 +261,8 @@ class KirbyBase
|
|
230
261
|
return @table_hash[name]
|
231
262
|
else
|
232
263
|
@table_hash[name] = \
|
233
|
-
KBTable.create_called_from_database_instance(self,
|
234
|
-
|
264
|
+
KBTable.create_called_from_database_instance(self, name,
|
265
|
+
File.join(@path, name.to_s + @ext))
|
235
266
|
return @table_hash[name]
|
236
267
|
end
|
237
268
|
end
|
@@ -445,6 +476,13 @@ class KBEngine
|
|
445
476
|
@recno_indexes.delete(tablename)
|
446
477
|
end
|
447
478
|
|
479
|
+
#-----------------------------------------------------------------------
|
480
|
+
# update_recno_index
|
481
|
+
#-----------------------------------------------------------------------
|
482
|
+
def update_recno_index(table, recno, fpos)
|
483
|
+
@recno_indexes[table.name].update_index_rec(recno, fpos)
|
484
|
+
end
|
485
|
+
|
448
486
|
#-----------------------------------------------------------------------
|
449
487
|
# recno_index_exists?
|
450
488
|
#-----------------------------------------------------------------------
|
@@ -508,20 +546,6 @@ class KBEngine
|
|
508
546
|
end
|
509
547
|
end
|
510
548
|
|
511
|
-
#-----------------------------------------------------------------------
|
512
|
-
# index_exists?
|
513
|
-
#-----------------------------------------------------------------------
|
514
|
-
def index_exists?(table, index_fields)
|
515
|
-
@indexes.include?("#{table.name}_#{index_fields.join('_')}".to_sym)
|
516
|
-
end
|
517
|
-
|
518
|
-
#-----------------------------------------------------------------------
|
519
|
-
# update_recno_index
|
520
|
-
#-----------------------------------------------------------------------
|
521
|
-
def update_recno_index(table, recno, fpos)
|
522
|
-
@recno_indexes[table.name].update_index_rec(recno, fpos)
|
523
|
-
end
|
524
|
-
|
525
549
|
#-----------------------------------------------------------------------
|
526
550
|
# update_to_indexes
|
527
551
|
#-----------------------------------------------------------------------
|
@@ -532,6 +556,13 @@ class KBEngine
|
|
532
556
|
end
|
533
557
|
end
|
534
558
|
|
559
|
+
#-----------------------------------------------------------------------
|
560
|
+
# index_exists?
|
561
|
+
#-----------------------------------------------------------------------
|
562
|
+
def index_exists?(table, index_fields)
|
563
|
+
@indexes.include?("#{table.name}_#{index_fields.join('_')}".to_sym)
|
564
|
+
end
|
565
|
+
|
535
566
|
#-----------------------------------------------------------------------
|
536
567
|
# get_index
|
537
568
|
#-----------------------------------------------------------------------
|
@@ -539,6 +570,13 @@ class KBEngine
|
|
539
570
|
return @indexes["#{table.name}_#{index_name}".to_sym].get_idx
|
540
571
|
end
|
541
572
|
|
573
|
+
#-----------------------------------------------------------------------
|
574
|
+
# get_index_timestamp
|
575
|
+
#-----------------------------------------------------------------------
|
576
|
+
def get_index_timestamp(table, index_name)
|
577
|
+
return @indexes["#{table.name}_#{index_name}".to_sym].get_timestamp
|
578
|
+
end
|
579
|
+
|
542
580
|
#-----------------------------------------------------------------------
|
543
581
|
# get_recno_index
|
544
582
|
#-----------------------------------------------------------------------
|
@@ -686,6 +724,19 @@ class KBEngine
|
|
686
724
|
return get_recs(table).size
|
687
725
|
end
|
688
726
|
|
727
|
+
#-----------------------------------------------------------------------
|
728
|
+
# reset_recno_ctr
|
729
|
+
#-----------------------------------------------------------------------
|
730
|
+
def reset_recno_ctr(table)
|
731
|
+
with_write_locked_table(table) do |fptr|
|
732
|
+
last_rec_no, rest_of_line = get_header_record(table, fptr
|
733
|
+
).split('|', 2)
|
734
|
+
write_header_record(table, fptr,
|
735
|
+
['%06d' % 0, rest_of_line].join('|'))
|
736
|
+
return true
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
689
740
|
#-----------------------------------------------------------------------
|
690
741
|
# get_header_vars
|
691
742
|
#-----------------------------------------------------------------------
|
@@ -769,8 +820,11 @@ class KBEngine
|
|
769
820
|
# Skip header rec.
|
770
821
|
fptr.readline
|
771
822
|
|
772
|
-
recnos
|
773
|
-
|
823
|
+
# Take all the recnos you want to get, add the file positions
|
824
|
+
# to them, and sort by file position, so that when we seek
|
825
|
+
# through the physical file we are going in ascending file
|
826
|
+
# position order, which should be fastest.
|
827
|
+
recnos.collect { |r| [recno_idx[r], r] }.sort.each do |r|
|
774
828
|
fptr.seek(r[0])
|
775
829
|
line = fptr.readline
|
776
830
|
line.chomp!
|
@@ -1079,29 +1133,53 @@ class KBEngine
|
|
1079
1133
|
end
|
1080
1134
|
|
1081
1135
|
#-----------------------------------------------------------------------
|
1082
|
-
#
|
1136
|
+
# read_memo_file
|
1083
1137
|
#-----------------------------------------------------------------------
|
1084
|
-
def
|
1138
|
+
def read_memo_file(filepath)
|
1085
1139
|
begin
|
1086
|
-
f = File.new(filepath)
|
1087
|
-
return f.
|
1140
|
+
f = File.new(File.join(@db.memo_blob_path, filepath))
|
1141
|
+
return f.read
|
1088
1142
|
ensure
|
1089
1143
|
f.close
|
1090
1144
|
end
|
1091
1145
|
end
|
1092
1146
|
|
1093
1147
|
#-----------------------------------------------------------------------
|
1094
|
-
#
|
1148
|
+
# write_memo_file
|
1095
1149
|
#-----------------------------------------------------------------------
|
1096
|
-
def
|
1150
|
+
def write_memo_file(filepath, contents)
|
1097
1151
|
begin
|
1098
|
-
f = File.new(filepath, '
|
1152
|
+
f = File.new(File.join(@db.memo_blob_path, filepath), 'w')
|
1153
|
+
f.write(contents)
|
1154
|
+
ensure
|
1155
|
+
f.close
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
#-----------------------------------------------------------------------
|
1160
|
+
# read_blob_file
|
1161
|
+
#-----------------------------------------------------------------------
|
1162
|
+
def read_blob_file(filepath)
|
1163
|
+
begin
|
1164
|
+
f = File.new(File.join(@db.memo_blob_path, filepath), 'rb')
|
1099
1165
|
return f.read
|
1100
1166
|
ensure
|
1101
1167
|
f.close
|
1102
1168
|
end
|
1103
1169
|
end
|
1104
1170
|
|
1171
|
+
#-----------------------------------------------------------------------
|
1172
|
+
# write_blob_file
|
1173
|
+
#-----------------------------------------------------------------------
|
1174
|
+
def write_blob_file(filepath, contents)
|
1175
|
+
begin
|
1176
|
+
f = File.new(File.join(@db.memo_blob_path, filepath), 'wb')
|
1177
|
+
f.write(contents)
|
1178
|
+
ensure
|
1179
|
+
f.close
|
1180
|
+
end
|
1181
|
+
end
|
1182
|
+
|
1105
1183
|
|
1106
1184
|
#-----------------------------------------------------------------------
|
1107
1185
|
# PRIVATE METHODS
|
@@ -1217,17 +1295,6 @@ class KBEngine
|
|
1217
1295
|
end
|
1218
1296
|
end
|
1219
1297
|
|
1220
|
-
#-----------------------------------------------------------------------
|
1221
|
-
# reset_rec_no_ctr
|
1222
|
-
#-----------------------------------------------------------------------
|
1223
|
-
def reset_rec_no_ctr(table, fptr)
|
1224
|
-
last_rec_no, rest_of_line = get_header_record(table, fptr).split(
|
1225
|
-
'|', 2)
|
1226
|
-
write_header_record(table, fptr, ['%06d' % 0, rest_of_line].join(
|
1227
|
-
'|'))
|
1228
|
-
return true
|
1229
|
-
end
|
1230
|
-
|
1231
1298
|
#-----------------------------------------------------------------------
|
1232
1299
|
# incr_rec_no_ctr
|
1233
1300
|
#-----------------------------------------------------------------------
|
@@ -1344,10 +1411,10 @@ module KBTypeConversionsMixin
|
|
1344
1411
|
else
|
1345
1412
|
return true
|
1346
1413
|
end
|
1414
|
+
when :Time
|
1415
|
+
return Time.parse(s)
|
1347
1416
|
when :Date
|
1348
1417
|
return Date.parse(s)
|
1349
|
-
when :Time
|
1350
|
-
return Time.parse(s)
|
1351
1418
|
when :DateTime
|
1352
1419
|
return DateTime.parse(s)
|
1353
1420
|
when :YAML
|
@@ -1370,9 +1437,13 @@ module KBTypeConversionsMixin
|
|
1370
1437
|
return YAML.load(s)
|
1371
1438
|
end
|
1372
1439
|
when :Memo
|
1373
|
-
|
1440
|
+
memo = KBMemo.new(@tbl.db, s)
|
1441
|
+
memo.read_from_file
|
1442
|
+
return memo
|
1374
1443
|
when :Blob
|
1375
|
-
|
1444
|
+
blob = KBBlob.new(@tbl.db, s)
|
1445
|
+
blob.read_from_file
|
1446
|
+
return blob
|
1376
1447
|
else
|
1377
1448
|
raise "Invalid field type: %s" % data_type
|
1378
1449
|
end
|
@@ -1391,7 +1462,7 @@ class KBTable
|
|
1391
1462
|
private_class_method :new
|
1392
1463
|
|
1393
1464
|
VALID_FIELD_TYPES = [:String, :Integer, :Float, :Boolean, :Date, :Time,
|
1394
|
-
:DateTime, :Memo, :ResultSet, :YAML]
|
1465
|
+
:DateTime, :Memo, :Blob, :ResultSet, :YAML]
|
1395
1466
|
|
1396
1467
|
VALID_INDEX_TYPES = [:String, :Integer, :Float, :Boolean, :Date, :Time,
|
1397
1468
|
:DateTime]
|
@@ -1452,6 +1523,8 @@ class KBTable
|
|
1452
1523
|
@filename = filename
|
1453
1524
|
@encrypted = false
|
1454
1525
|
@lookup_key = :recno
|
1526
|
+
@idx_timestamps = {}
|
1527
|
+
@idx_arrs = {}
|
1455
1528
|
|
1456
1529
|
# Alias delete_all to clear method.
|
1457
1530
|
alias delete_all clear
|
@@ -1547,6 +1620,14 @@ class KBTable
|
|
1547
1620
|
# Check the field values to make sure they are proper types.
|
1548
1621
|
validate_input(input_rec)
|
1549
1622
|
|
1623
|
+
if @field_types.include?(:Memo)
|
1624
|
+
input_rec.each_value { |r| r.write_to_file if r.is_a?(KBMemo) }
|
1625
|
+
end
|
1626
|
+
|
1627
|
+
if @field_types.include?(:Blob)
|
1628
|
+
input_rec.each_value { |r| r.write_to_file if r.is_a?(KBBlob) }
|
1629
|
+
end
|
1630
|
+
|
1550
1631
|
return @db.engine.insert_record(self, @field_names.zip(@field_types
|
1551
1632
|
).collect do |fn, ft|
|
1552
1633
|
convert_to_string(ft, input_rec.fetch(fn, ''))
|
@@ -1624,6 +1705,14 @@ class KBTable
|
|
1624
1705
|
# type.
|
1625
1706
|
validate_input(update_rec)
|
1626
1707
|
|
1708
|
+
if @field_types.include?(:Memo)
|
1709
|
+
update_rec.each_value { |r| r.write_to_file if r.is_a?(KBMemo) }
|
1710
|
+
end
|
1711
|
+
|
1712
|
+
if @field_types.include?(:Blob)
|
1713
|
+
update_rec.each_value { |r| r.write_to_file if r.is_a?(KBBlob) }
|
1714
|
+
end
|
1715
|
+
|
1627
1716
|
updated_recs = []
|
1628
1717
|
|
1629
1718
|
# For each one of the recs that matched the update query, apply the
|
@@ -1680,7 +1769,7 @@ class KBTable
|
|
1680
1769
|
delete { true }
|
1681
1770
|
pack
|
1682
1771
|
|
1683
|
-
@db.engine.reset_recno_ctr if reset_recno_ctr
|
1772
|
+
@db.engine.reset_recno_ctr(self) if reset_recno_ctr
|
1684
1773
|
end
|
1685
1774
|
|
1686
1775
|
#-----------------------------------------------------------------------
|
@@ -1794,12 +1883,15 @@ class KBTable
|
|
1794
1883
|
# *csv_filename*:: filename of csv file to import.
|
1795
1884
|
#
|
1796
1885
|
def import_csv(csv_filename)
|
1886
|
+
records_inserted = 0
|
1797
1887
|
tbl_rec = @table_class.new(self)
|
1798
1888
|
|
1799
1889
|
CSV.open(csv_filename, 'r') do |row|
|
1800
1890
|
tbl_rec.populate([nil] + row)
|
1801
1891
|
insert(tbl_rec)
|
1892
|
+
records_inserted += 1
|
1802
1893
|
end
|
1894
|
+
return records_inserted
|
1803
1895
|
end
|
1804
1896
|
|
1805
1897
|
#-----------------------------------------------------------------------
|
@@ -1813,7 +1905,7 @@ class KBTable
|
|
1813
1905
|
def create_indexes
|
1814
1906
|
# Create the recno index. A recno index always gets created even if
|
1815
1907
|
# there are no user-defined indexes for the table.
|
1816
|
-
@db.engine.init_recno_index(self)
|
1908
|
+
@db.engine.init_recno_index(self)
|
1817
1909
|
|
1818
1910
|
# There can be up to 5 different indexes on a table. Any of these
|
1819
1911
|
# indexes can be single or compound.
|
@@ -1830,7 +1922,7 @@ class KBTable
|
|
1830
1922
|
next if index_col_names.empty?
|
1831
1923
|
|
1832
1924
|
# Create this index on the engine.
|
1833
|
-
@db.engine.init_index(self, index_col_names)
|
1925
|
+
@db.engine.init_index(self, index_col_names)
|
1834
1926
|
|
1835
1927
|
# For each index found, add an instance method for it so that
|
1836
1928
|
# it can be used for #selects.
|
@@ -1844,7 +1936,10 @@ class KBTable
|
|
1844
1936
|
[:#{index_col_names.join(',:')}], filter, select_cond)
|
1845
1937
|
end
|
1846
1938
|
END_OF_STRING
|
1847
|
-
self.class.class_eval(select_meth_str)
|
1939
|
+
self.class.class_eval(select_meth_str) unless @db.server?
|
1940
|
+
|
1941
|
+
@idx_timestamps[index_col_names.join('_')] = nil
|
1942
|
+
@idx_arrs[index_col_names.join('_')] = nil
|
1848
1943
|
end
|
1849
1944
|
end
|
1850
1945
|
|
@@ -1893,14 +1988,11 @@ class KBTable
|
|
1893
1988
|
END_OF_STRING
|
1894
1989
|
else
|
1895
1990
|
begin
|
1896
|
-
@db.get_table(lookup_table)
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
if @db.get_table(lookup_table).respond_to?(
|
1903
|
-
'select_by_%s_index' % key_field)
|
1991
|
+
unless @db.get_table(lookup_table).respond_to?(
|
1992
|
+
'select_by_%s_index' % key_field)
|
1993
|
+
raise RuntimeError
|
1994
|
+
end
|
1995
|
+
|
1904
1996
|
get_meth_str = <<-END_OF_STRING
|
1905
1997
|
def #{field_name}
|
1906
1998
|
table = @tbl.db.get_table(:#{lookup_table})
|
@@ -1908,7 +2000,7 @@ class KBTable
|
|
1908
2000
|
r.#{key_field} == @#{field_name} }.first
|
1909
2001
|
end
|
1910
2002
|
END_OF_STRING
|
1911
|
-
|
2003
|
+
rescue RuntimeError
|
1912
2004
|
get_meth_str = <<-END_OF_STRING
|
1913
2005
|
def #{field_name}
|
1914
2006
|
table = @tbl.db.get_table(:#{lookup_table})
|
@@ -1926,14 +2018,11 @@ class KBTable
|
|
1926
2018
|
link_table, link_field = rest.split('.')
|
1927
2019
|
|
1928
2020
|
begin
|
1929
|
-
@db.get_table(link_table)
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
if @db.get_table(link_table).respond_to?(
|
1936
|
-
'select_by_%s_index' % link_field)
|
2021
|
+
unless @db.get_table(link_table).respond_to?(
|
2022
|
+
'select_by_%s_index' % link_field)
|
2023
|
+
raise RuntimeError
|
2024
|
+
end
|
2025
|
+
|
1937
2026
|
get_meth_str = <<-END_OF_STRING
|
1938
2027
|
def #{field_name}
|
1939
2028
|
table = @tbl.db.get_table(:#{link_table})
|
@@ -1941,7 +2030,7 @@ class KBTable
|
|
1941
2030
|
r.send(:#{link_field}) == @#{lookup_field} }
|
1942
2031
|
end
|
1943
2032
|
END_OF_STRING
|
1944
|
-
|
2033
|
+
rescue RuntimeError
|
1945
2034
|
get_meth_str = <<-END_OF_STRING
|
1946
2035
|
def #{field_name}
|
1947
2036
|
table = @tbl.db.get_table(:#{link_table})
|
@@ -2011,7 +2100,11 @@ class KBTable
|
|
2011
2100
|
).gsub("|", '&pipe;')
|
2012
2101
|
else
|
2013
2102
|
return x
|
2014
|
-
end
|
2103
|
+
end
|
2104
|
+
when :Memo
|
2105
|
+
return x.filepath
|
2106
|
+
when :Blob
|
2107
|
+
return x.filepath
|
2015
2108
|
else
|
2016
2109
|
return x.to_s
|
2017
2110
|
end
|
@@ -2103,9 +2196,15 @@ class KBTable
|
|
2103
2196
|
|
2104
2197
|
next if data[f].nil?
|
2105
2198
|
case @field_types[@field_names.index(f)]
|
2106
|
-
when /:String|:
|
2199
|
+
when /:String|:Blob/
|
2107
2200
|
raise 'Invalid String value for: %s' % f unless \
|
2108
2201
|
data[f].respond_to?(:to_str)
|
2202
|
+
when :Memo
|
2203
|
+
raise 'Invalid Memo value for: %s' % f unless \
|
2204
|
+
data[f].is_a?(KBMemo)
|
2205
|
+
when :Blob
|
2206
|
+
raise 'Invalid Blob value for: %s' % f unless \
|
2207
|
+
data[f].is_a?(KBBlob)
|
2109
2208
|
when :Boolean
|
2110
2209
|
raise 'Invalid Boolean value for: %s' % f unless \
|
2111
2210
|
data[f].is_a?(TrueClass) or data[f].kind_of?(FalseClass)
|
@@ -2115,12 +2214,12 @@ class KBTable
|
|
2115
2214
|
when :Float
|
2116
2215
|
raise 'Invalid Float value for: %s' % f unless \
|
2117
2216
|
data[f].respond_to?(:to_f)
|
2217
|
+
when :Time
|
2218
|
+
raise 'Invalid Time value for: %s' % f unless \
|
2219
|
+
data[f].is_a?(Time)
|
2118
2220
|
when :Date
|
2119
2221
|
raise 'Invalid Date value for: %s' % f unless \
|
2120
2222
|
data[f].is_a?(Date)
|
2121
|
-
when :Time
|
2122
|
-
raise 'Invalid Time value for: %s' % f unless \
|
2123
|
-
data[f].is_a?(Time)
|
2124
2223
|
when :DateTime
|
2125
2224
|
raise 'Invalid DateTime value for: %s' % f unless \
|
2126
2225
|
data[f].is_a?(DateTime)
|
@@ -2241,7 +2340,28 @@ class KBTable
|
|
2241
2340
|
idx_struct = Struct.new(*(index_fields + [:recno]))
|
2242
2341
|
|
2243
2342
|
begin
|
2244
|
-
@db.
|
2343
|
+
if @db.client?
|
2344
|
+
# If client, check to see if the copy of the index we have
|
2345
|
+
# is up-to-date. If it is not up-to-date, grab a new copy
|
2346
|
+
# of the index array from the engine.
|
2347
|
+
unless @idx_timestamps[index_fields.join('_')] == \
|
2348
|
+
@db.engine.get_index_timestamp(self, index_fields.join(
|
2349
|
+
'_'))
|
2350
|
+
@idx_timestamps[index_fields.join('_')] = \
|
2351
|
+
@db.engine.get_index_timestamp(self, index_fields.join(
|
2352
|
+
'_'))
|
2353
|
+
|
2354
|
+
@idx_arrs[index_fields.join('_')] = \
|
2355
|
+
@db.engine.get_index(self, index_fields.join('_'))
|
2356
|
+
end
|
2357
|
+
else
|
2358
|
+
# If running single-user, grab the index array from the
|
2359
|
+
# engine.
|
2360
|
+
@idx_arrs[index_fields.join('_')] = \
|
2361
|
+
@db.engine.get_index(self, index_fields.join('_'))
|
2362
|
+
end
|
2363
|
+
|
2364
|
+
@idx_arrs[index_fields.join('_')].each do |rec|
|
2245
2365
|
good_matches << rec[-1] if select_cond.call(
|
2246
2366
|
idx_struct.new(*rec))
|
2247
2367
|
end
|
@@ -2327,14 +2447,23 @@ end
|
|
2327
2447
|
# KBMemo
|
2328
2448
|
#---------------------------------------------------------------------------
|
2329
2449
|
class KBMemo
|
2330
|
-
|
2450
|
+
attr_accessor :filepath, :contents
|
2331
2451
|
|
2332
2452
|
#-----------------------------------------------------------------------
|
2333
2453
|
# initialize
|
2334
2454
|
#-----------------------------------------------------------------------
|
2335
|
-
def initialize(db, filepath)
|
2455
|
+
def initialize(db, filepath, contents='')
|
2456
|
+
@db = db
|
2336
2457
|
@filepath = filepath
|
2337
|
-
@
|
2458
|
+
@contents = contents
|
2459
|
+
end
|
2460
|
+
|
2461
|
+
def read_from_file
|
2462
|
+
@contents = @db.engine.read_memo_file(@filepath)
|
2463
|
+
end
|
2464
|
+
|
2465
|
+
def write_to_file
|
2466
|
+
@db.engine.write_memo_file(@filepath, @contents)
|
2338
2467
|
end
|
2339
2468
|
end
|
2340
2469
|
|
@@ -2342,14 +2471,23 @@ end
|
|
2342
2471
|
# KBBlob
|
2343
2472
|
#---------------------------------------------------------------------------
|
2344
2473
|
class KBBlob
|
2345
|
-
|
2474
|
+
attr_accessor :filepath, :contents
|
2346
2475
|
|
2347
2476
|
#-----------------------------------------------------------------------
|
2348
2477
|
# initialize
|
2349
2478
|
#-----------------------------------------------------------------------
|
2350
|
-
def initialize(db, filepath)
|
2479
|
+
def initialize(db, filepath, contents='')
|
2480
|
+
@db = db
|
2351
2481
|
@filepath = filepath
|
2352
|
-
@
|
2482
|
+
@contents = contents
|
2483
|
+
end
|
2484
|
+
|
2485
|
+
def read_from_file
|
2486
|
+
@contents = @db.engine.read_blob_file(@filepath)
|
2487
|
+
end
|
2488
|
+
|
2489
|
+
def write_to_file
|
2490
|
+
@db.engine.write_blob_file(@filepath, @contents)
|
2353
2491
|
end
|
2354
2492
|
end
|
2355
2493
|
|
@@ -2366,6 +2504,7 @@ class KBIndex
|
|
2366
2504
|
# initialize
|
2367
2505
|
#-----------------------------------------------------------------------
|
2368
2506
|
def initialize(table, index_fields)
|
2507
|
+
@last_update = Time.new
|
2369
2508
|
@idx_arr = []
|
2370
2509
|
@table = table
|
2371
2510
|
@index_fields = index_fields
|
@@ -2382,6 +2521,13 @@ class KBIndex
|
|
2382
2521
|
return @idx_arr
|
2383
2522
|
end
|
2384
2523
|
|
2524
|
+
#-----------------------------------------------------------------------
|
2525
|
+
# get_timestamp
|
2526
|
+
#-----------------------------------------------------------------------
|
2527
|
+
def get_timestamp
|
2528
|
+
return @last_update
|
2529
|
+
end
|
2530
|
+
|
2385
2531
|
#-----------------------------------------------------------------------
|
2386
2532
|
# rebuild
|
2387
2533
|
#-----------------------------------------------------------------------
|
@@ -2429,8 +2575,10 @@ class KBIndex
|
|
2429
2575
|
# Here's how we break out of the loop...
|
2430
2576
|
rescue EOFError
|
2431
2577
|
end
|
2578
|
+
|
2579
|
+
@last_update = Time.new
|
2432
2580
|
end
|
2433
|
-
|
2581
|
+
|
2434
2582
|
#-----------------------------------------------------------------------
|
2435
2583
|
# add_index_rec
|
2436
2584
|
#-----------------------------------------------------------------------
|
@@ -2438,6 +2586,8 @@ class KBIndex
|
|
2438
2586
|
@idx_arr << @col_poss.zip(@col_types).collect do |col_pos, col_type|
|
2439
2587
|
convert_to(col_type, rec[col_pos])
|
2440
2588
|
end + [rec.first.to_i]
|
2589
|
+
|
2590
|
+
@last_update = Time.new
|
2441
2591
|
end
|
2442
2592
|
|
2443
2593
|
#-----------------------------------------------------------------------
|
@@ -2446,6 +2596,7 @@ class KBIndex
|
|
2446
2596
|
def delete_index_rec(recno)
|
2447
2597
|
i = @idx_arr.rassoc(recno.to_i)
|
2448
2598
|
@idx_arr.delete_at(@idx_arr.index(i)) unless i.nil?
|
2599
|
+
@last_update = Time.new
|
2449
2600
|
end
|
2450
2601
|
|
2451
2602
|
#-----------------------------------------------------------------------
|