og 0.24.0 → 0.25.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/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
|
#-----------------------------------------------------------------------
|