KirbyBase 2.5 → 2.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +2 -2
- data/bin/kbserver.rb +0 -2
- data/changes.txt +18 -0
- data/examples/aaa_try_this_first/kbtest.rb +10 -12
- data/examples/memo_test/memo_test.rb +12 -1
- data/kirbybaserubymanual.html +68 -38
- data/lib/kirbybase.rb +676 -540
- data/test/base_test.rb +17 -0
- data/test/tc_local_db.rb +80 -0
- data/test/tc_local_table.rb +716 -0
- data/test/ts_local.rb +4 -0
- metadata +8 -3
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= KirbyBase 2.5
|
1
|
+
= KirbyBase 2.5.1
|
2
2
|
|
3
3
|
A small, plain-text, dbms written in Ruby. It can be used either embedded
|
4
4
|
or client/server.
|
@@ -26,8 +26,8 @@ See the examples directory for examples of how to use KirbyBase.
|
|
26
26
|
* kirbybaserubymanual.html - documentation
|
27
27
|
* kirbybase.rb - dbms library
|
28
28
|
* kbserver.rb - multi-threaded database server script.
|
29
|
+
* test directory - unit tests
|
29
30
|
* examples directory - many example scripts demonstrating features.
|
30
|
-
* doc directory - RDoc generated documentation in html format.
|
31
31
|
* images directory - images used in manual.
|
32
32
|
|
33
33
|
|
data/bin/kbserver.rb
CHANGED
data/changes.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
2005-12-28:: Version 2.5.1
|
2
|
+
* Fixed a bug that had broken encrypted tables.
|
3
|
+
* Changed KBTable#pack method so that it raises an error if trying to
|
4
|
+
execute when :connect_type==:client.
|
5
|
+
* Fixed a bug where it was possible to insert records missing a required
|
6
|
+
field if using a hash. Thanks to Adam Shelly for this.
|
7
|
+
* Fixed a bug that occurred when you tried to update records using a
|
8
|
+
block and you tried to reference a field in the current record inside
|
9
|
+
the block. Much thanks to Assaph Mehr for reporting this.
|
10
|
+
* Fixed a bug that allowed you to have duplicate column names. Thanks to
|
11
|
+
Assaph Mehr for spotting this.
|
12
|
+
* Changed the way KBTable#set works with memo/blob fields.
|
13
|
+
* Started creating unit tests.
|
14
|
+
* Changed the KBTable#clear method to return number of records deleted.
|
15
|
+
Thanks to Assaph Mehr for this enhancement.
|
16
|
+
* Moved #build_header_string from KBEngine class to KirbyBase class.
|
17
|
+
* Added KirbyBase::VERSION constant.
|
18
|
+
|
1
19
|
2005-12-01:: Version 2.5
|
2
20
|
* Fixed a subtle bug in KBTable#create_indexes.
|
3
21
|
* Added the following new methods to KBTable: add_index, drop_index,
|
@@ -56,7 +56,7 @@ plane_tbl.insert { |r|
|
|
56
56
|
r.country = 'USA'
|
57
57
|
r.role = 'Bomber'
|
58
58
|
r.speed = 315
|
59
|
-
r.range =
|
59
|
+
r.range = r.speed * 3
|
60
60
|
r.began_service = Date.new(1937, 5, 1)
|
61
61
|
r.still_flying = true
|
62
62
|
}
|
@@ -92,27 +92,25 @@ plane_tbl.insert('Zero', 'Japan', 'Fighter', 377, 912,
|
|
92
92
|
# 1) Update record using a Hash, Struct, or an Array.
|
93
93
|
plane_tbl.update(:speed => 405, :range => 1210) { |r| r.name == 'P-51' }
|
94
94
|
|
95
|
-
# 2) Update record using a
|
96
|
-
plane_tbl.update {|r| r.name == 'P-51'}.set {|r|
|
97
|
-
r.speed = 405
|
98
|
-
r.range = 1210
|
99
|
-
}
|
100
|
-
|
101
|
-
# 3) Update record using a Hash, Struct, or an Array, via the set
|
95
|
+
# 2) Update record using a Hash, Struct, or an Array, via the set
|
102
96
|
# command.
|
103
97
|
plane_tbl.update {|r| r.name == 'P-51'}.set(:speed => 405, :range => 1210)
|
104
98
|
|
105
|
-
#
|
99
|
+
# 3) Update record by treating table as if it were a Hash and the keys were
|
106
100
|
# recno's.
|
107
101
|
plane_tbl[2] = {:speed => 405, :range => 1210}
|
108
102
|
|
103
|
+
# 4) Update record using a code block, via the set command. Notice how you
|
104
|
+
# have access to the current record's values within the block.
|
105
|
+
plane_tbl.update {|r| r.name == 'P-51'}.set {|r|
|
106
|
+
r.speed = r.speed+7
|
107
|
+
r.range = r.range-2
|
108
|
+
}
|
109
|
+
|
109
110
|
#--------------------- Delete Examples -------------------------------------
|
110
111
|
# Delete 'FW-190' record.
|
111
112
|
plane_tbl.delete { |r| r.name == 'FW-190' }
|
112
113
|
|
113
|
-
# Remove deleted records from the table.
|
114
|
-
plane_tbl.pack
|
115
|
-
|
116
114
|
#---------------------- Select Example 0 -----------------------------------
|
117
115
|
print_divider('Select Example 0')
|
118
116
|
# Select all records, including all fields in result set.
|
@@ -54,10 +54,21 @@ rec = plane_tbl.select { |r| r.name == 'P-51' }.first
|
|
54
54
|
|
55
55
|
# Grab the contents of the memo field and add a line to it.
|
56
56
|
memo = rec.descr
|
57
|
-
memo.contents += "This last line should show up if the memo was changed
|
57
|
+
memo.contents += "This last line should show up if the memo was changed.\n"
|
58
58
|
|
59
59
|
# Now, update the record with the changed contents of the memo field.
|
60
60
|
plane_tbl.update {|r| r.name == 'P-51'}.set(:descr => memo)
|
61
61
|
|
62
62
|
# Do another select to show that the memo field indeed changed.
|
63
63
|
puts plane_tbl.select { |r| r.name == 'P-51' }.first.descr.contents
|
64
|
+
|
65
|
+
|
66
|
+
puts "\n\nNow we will change every record's memo field:\n\n"
|
67
|
+
|
68
|
+
plane_tbl.update_all { |r|
|
69
|
+
r.descr.contents += "I have added a line to the %s memo.\n\n" % r.name
|
70
|
+
}
|
71
|
+
|
72
|
+
# Do another select to show that the memo field for each record has
|
73
|
+
# indeed changed.
|
74
|
+
plane_tbl.select.each { |r| puts r.descr.contents }
|
data/kirbybaserubymanual.html
CHANGED
@@ -262,8 +262,7 @@ div.exampleblock-content {
|
|
262
262
|
<h1>KirbyBase Manual (Ruby Version)</h1>
|
263
263
|
<span id="author">Jamey Cribbs</span><br />
|
264
264
|
<span id="email"><tt><<a href="mailto:jcribbs@twmi.rr.com">jcribbs@twmi.rr.com</a>></tt></span><br />
|
265
|
-
|
266
|
-
1 December 2005
|
265
|
+
v2.5.1 December 2005
|
267
266
|
</div>
|
268
267
|
<div id="preamble">
|
269
268
|
<div class="sectionbody">
|
@@ -307,6 +306,16 @@ div.exampleblock-content {
|
|
307
306
|
</li>
|
308
307
|
<li>
|
309
308
|
<p>
|
309
|
+
<a href="#encrypt">Turning on encryption</a>
|
310
|
+
</p>
|
311
|
+
</li>
|
312
|
+
<li>
|
313
|
+
<p>
|
314
|
+
<a href="#record-class">Specifying a custom record class</a>
|
315
|
+
</p>
|
316
|
+
</li>
|
317
|
+
<li>
|
318
|
+
<p>
|
310
319
|
<a href="#defaults">Specifying default values</a>
|
311
320
|
</p>
|
312
321
|
</li>
|
@@ -576,12 +585,12 @@ div.exampleblock-content {
|
|
576
585
|
</li>
|
577
586
|
<li>
|
578
587
|
<p>
|
579
|
-
<a href="#
|
588
|
+
<a href="#single-user-diagram">Single-user memory space diagram</a>
|
580
589
|
</p>
|
581
590
|
</li>
|
582
591
|
<li>
|
583
592
|
<p>
|
584
|
-
<a href="#
|
593
|
+
<a href="#client-server-diagram">Client/Server memory space diagram</a>
|
585
594
|
</p>
|
586
595
|
</li>
|
587
596
|
<li>
|
@@ -605,7 +614,7 @@ Some of its features include:</p>
|
|
605
614
|
<li>
|
606
615
|
<p>
|
607
616
|
Since KirbyBase is written in Ruby, it runs anywhere that Ruby runs. It
|
608
|
-
is easy to distribute, since the entire
|
617
|
+
is easy to distribute, since the entire DBMS is in one (approx. 100k) code
|
609
618
|
file.
|
610
619
|
</p>
|
611
620
|
</li>
|
@@ -653,7 +662,7 @@ language like SQL, you can express your query using Ruby code blocks.
|
|
653
662
|
<li>
|
654
663
|
<p>
|
655
664
|
All inserted records have an auto-incrementing primary key that is
|
656
|
-
guaranteed to uniquely identify the record throughout
|
665
|
+
guaranteed to uniquely identify the record throughout its lifetime.
|
657
666
|
</p>
|
658
667
|
</li>
|
659
668
|
<li>
|
@@ -724,7 +733,7 @@ like this:</p>
|
|
724
733
|
<pre><tt>db = KirbyBase.new(:client, 'localhost', 44444)</tt></pre>
|
725
734
|
</div></div>
|
726
735
|
<p>Of course, you would substitute your server's ip address and port number.</p>
|
727
|
-
<p>To specify a different location other than the current directory for the
|
736
|
+
<p>To specify a different location (other than the current directory) for the
|
728
737
|
database files, you need to pass the location as an argument, like so:</p>
|
729
738
|
<div class="listingblock">
|
730
739
|
<div class="content">
|
@@ -737,13 +746,28 @@ a different extension, pass this as an argument, like so:</p>
|
|
737
746
|
<div class="content">
|
738
747
|
<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.dat')</tt></pre>
|
739
748
|
</div></div>
|
749
|
+
<p>To specify a different location other than the current directory for any
|
750
|
+
memo/blob files, you need to pass the location as an argument, like so:</p>
|
751
|
+
<div class="listingblock">
|
752
|
+
<div class="content">
|
753
|
+
<pre><tt>db = KirbyBase.new(:local, nil, './', '.tbl', './memos')</tt></pre>
|
754
|
+
</div></div>
|
755
|
+
<p>If you don't want KirbyBase to spend time initially creating all of the
|
756
|
+
indexes for the tables in the database, you can pass false as the
|
757
|
+
delay_index_creation argument:</p>
|
758
|
+
<div class="listingblock">
|
759
|
+
<div class="content">
|
760
|
+
<pre><tt>db = KirbyBase.new(:local, nil, './', '.tbl', './', true)</tt></pre>
|
761
|
+
</div></div>
|
762
|
+
<p>KirbyBase will skip initial index creation and will create a table's
|
763
|
+
indexes when the table is first referenced.</p>
|
740
764
|
<p>You can also specify the arguments via a code block. So, if you don't want
|
741
765
|
to have to specify a bunch of arguments just to get to the one you want,
|
742
766
|
put it in a code block attached to the method call. You could re-code the
|
743
767
|
previous example like so:</p>
|
744
768
|
<div class="listingblock">
|
745
769
|
<div class="content">
|
746
|
-
<pre><tt>db = KirbyBase.new { |d| d.
|
770
|
+
<pre><tt>db = KirbyBase.new { |d| d.delay_index_creation = true }</tt></pre>
|
747
771
|
</div></div>
|
748
772
|
</div>
|
749
773
|
<h2><a id="creating-a-new-table"></a>Creating a new table</h2>
|
@@ -787,6 +811,7 @@ select, update, and delete queries, but you can't modify this field.</td>
|
|
787
811
|
</tr></table>
|
788
812
|
</div>
|
789
813
|
</div></div>
|
814
|
+
<h3><a id="encrypt"></a>Turning on encryption</h3>
|
790
815
|
<p>You can also specify whether the table should be encrypted. This will save
|
791
816
|
the table to disk encrypted (more like obfuscated) using a Vignere Cipher.
|
792
817
|
This is similar to rot13, but it uses a key to determine character
|
@@ -799,6 +824,7 @@ encryption by using a code block attached to #create_table:</p>
|
|
799
824
|
t.encrypt = true
|
800
825
|
end</tt></pre>
|
801
826
|
</div></div>
|
827
|
+
<h3><a id="record-class"></a>Specifying a custom record class</h3>
|
802
828
|
<p>You can also specify that you want records of the table to be returned to
|
803
829
|
you as instances of a class. To do this, simply define a class before you
|
804
830
|
call #create_table. This class needs to have an accessor for each fieldname
|
@@ -869,9 +895,11 @@ or specify nil as the value, KirbyBase stores this as an empty string
|
|
869
895
|
(i.e. "") in the table's physical file, and returns it as a nil value when
|
870
896
|
you do a #select.</p>
|
871
897
|
<p>However, you can tell KirbyBase that you want a column to have a default
|
872
|
-
value.
|
873
|
-
|
874
|
-
|
898
|
+
value. Only KBTable#insert is affected by default values. Default values
|
899
|
+
do not apply to updating a record. So, for inserts, there are two cases
|
900
|
+
where a default value, if one is specified, will be applied: (1) if nil
|
901
|
+
is specified for a field's value, and (2) if no value is specified for a
|
902
|
+
field.</p>
|
875
903
|
<p>For example, to specify that the category column has a default value, you
|
876
904
|
would code:</p>
|
877
905
|
<div class="listingblock">
|
@@ -884,7 +912,7 @@ would code:</p>
|
|
884
912
|
cannot just simply say :String for the second half of the field definition.
|
885
913
|
Instead, we have to pass a hash, with a :DataType item set to :String.
|
886
914
|
Next, because we are specifying a default value, we have to include a hash
|
887
|
-
item called :Default with
|
915
|
+
item called :Default with its value set to whatever we want the default to
|
888
916
|
be. The default value must be of a type that is valid for the column.</p>
|
889
917
|
<h4><a id="requireds"></a>Required Fields</h4>
|
890
918
|
<p>Normally, when you insert or update a record, if you don't specify a value
|
@@ -907,7 +935,7 @@ code:</p>
|
|
907
935
|
cannot just simply say :String for the second half of the field definition.
|
908
936
|
Instead, we have to pass a hash, with a :DataType item set to :String.
|
909
937
|
Next, because we are specifying that a column is required, we have to
|
910
|
-
include a hash item called :Required with
|
938
|
+
include a hash item called :Required with its value set to true.</p>
|
911
939
|
<h4><a id="creating-indexes"></a>Indexes</h4>
|
912
940
|
<p>Indexes are in-memory arrays that have an entry that corresponds to each
|
913
941
|
table record, but just holds the field values specified when the index was
|
@@ -931,7 +959,7 @@ For example, to create an index on firstname and lastname for a table called
|
|
931
959
|
cannot just simply say :String for the second half of the field definition.
|
932
960
|
Instead, we have to pass a hash, with a :DataType item set to :String.
|
933
961
|
Next, because we are creating an index, we have to include a hash item
|
934
|
-
called :Index with
|
962
|
+
called :Index with its value set from 1 to 5. For compound indexes, like
|
935
963
|
the one in the above example, we need to make sure that all fields in the
|
936
964
|
index have the same number. To have another index for a table, just make
|
937
965
|
sure you increment the index number. So, for example, if we wanted to have
|
@@ -961,9 +989,10 @@ wait the first time a table is referenced. A user is more used to waiting
|
|
961
989
|
for an application to properly initialize, so this initial delay, I thought,
|
962
990
|
would feel more natural to the user, rather than a wait time in the middle
|
963
991
|
of the application.</p>
|
964
|
-
<p>
|
965
|
-
|
966
|
-
|
992
|
+
<p>If you find that this initial delay while indexes are created is
|
993
|
+
un-acceptable, you can pass delay_index_creation=true to KirbyBase.new.
|
994
|
+
This will bypass the initial index creation for all tables. Indexes for an
|
995
|
+
individual table will be created when the table is first referenced.</p>
|
967
996
|
<p>This is also an excellent reason to use the client/server capabilities of
|
968
997
|
KirbyBase. In client/server mode, the database initialization processing is
|
969
998
|
all done on the server, so, once this is done, any users starting their
|
@@ -1030,7 +1059,7 @@ You have a master table called :orders that holds one record for each
|
|
1030
1059
|
customer order that is placed. It has fields like: :order_id, :cust_id,
|
1031
1060
|
:order_date, etc.</p>
|
1032
1061
|
<p>Now, you also need a table that is going to have a record for each detail
|
1033
|
-
item type ordered. Let's call it :order_items and some of
|
1062
|
+
item type ordered. Let's call it :order_items and some of its fields would
|
1034
1063
|
be: :item_no, :qty, :order_id.</p>
|
1035
1064
|
<p>Notice that the detail table has a field called :order_id. This will hold
|
1036
1065
|
the order_id linking it back to the :orders.order_id table. If a customer
|
@@ -1062,7 +1091,7 @@ in the array above), equals the value of the :orders.order_id field (the
|
|
1062
1091
|
first item of the array above).</p>
|
1063
1092
|
<p>If you opened up the :orders table file in a text editor, you would notice
|
1064
1093
|
that, for each record, the :items field is blank. No data is ever stored in
|
1065
|
-
this field, since
|
1094
|
+
this field, since its value is always computed at runtime.</p>
|
1066
1095
|
<h4><a id="creating-calculated-fields"></a>Calculated Fields</h4>
|
1067
1096
|
<p>When you specify that a field is a Calculated field, you are telling
|
1068
1097
|
KirbyBase to compute the value of the field at runtime, based on the code
|
@@ -1085,7 +1114,7 @@ Here's how you would define this table:</p>
|
|
1085
1114
|
:qty field by the :price field.</p>
|
1086
1115
|
<p>If you opened up the :purchases file in a text editor, you would notice
|
1087
1116
|
that, for each record, the :total_cost field is blank. No data is ever
|
1088
|
-
stored in this field, since
|
1117
|
+
stored in this field, since its value is always computed at runtime.</p>
|
1089
1118
|
</div>
|
1090
1119
|
<h2><a id="obtaining-a-reference-to-an-existing-table"></a>Obtaining a reference to an existing table</h2>
|
1091
1120
|
<div class="sectionbody">
|
@@ -1141,7 +1170,8 @@ plane_tbl.insert(rec)</tt></pre>
|
|
1141
1170
|
r.speed = 315
|
1142
1171
|
r.range = 1400
|
1143
1172
|
r.began_service = Date.new(1937, 5, 1)
|
1144
|
-
r.still_flying = true
|
1173
|
+
r.still_flying = true
|
1174
|
+
end</tt></pre>
|
1145
1175
|
</div></div>
|
1146
1176
|
<h3>Insert a record using an instance of the table's custom record class:</h3>
|
1147
1177
|
<div class="listingblock">
|
@@ -1254,7 +1284,7 @@ indexes of a table. So, you would code your select query like this:</p>
|
|
1254
1284
|
<pre><tt>plane_tbl.select_by_speed_index { |r| r.speed > 400 }</tt></pre>
|
1255
1285
|
</div></div>
|
1256
1286
|
<p>Notice that you simply insert the name of the field as part of the method
|
1257
|
-
name.
|
1287
|
+
name. Its as simple as that.</p>
|
1258
1288
|
<p>For compound indexes, you just need to specify the
|
1259
1289
|
indexed field names in the select method in the same order as they are in
|
1260
1290
|
the table. So, let's say you have indexed the :plane table on :country and
|
@@ -1310,6 +1340,7 @@ employee_tbl = db.create_table(:employee, :employee_id, {:DataType=>:String,
|
|
1310
1340
|
r.manager.firstname == 'John' and r.manager.lastname == 'Doe'
|
1311
1341
|
end</tt></pre>
|
1312
1342
|
</div></div>
|
1343
|
+
<p>Notice how the manager attribute is itself a Struct with its own members.</p>
|
1313
1344
|
<p>To print out all departments including the manager's full name:</p>
|
1314
1345
|
<div class="listingblock">
|
1315
1346
|
<div class="content">
|
@@ -1330,7 +1361,7 @@ definitions from the earlier Link_many discussion:</p>
|
|
1330
1361
|
order_items_tbl = db.create_table(:order_items, :item_no, :Integer,
|
1331
1362
|
:qty, :Integer, :order_id, :Integer)</tt></pre>
|
1332
1363
|
</div></div>
|
1333
|
-
<p>To print an order and all of
|
1364
|
+
<p>To print an order and all of its associated detail items:</p>
|
1334
1365
|
<div class="listingblock">
|
1335
1366
|
<div class="content">
|
1336
1367
|
<pre><tt>result = order_tbl.select { |r| r.order_id == 501 }.first
|
@@ -1349,7 +1380,7 @@ is an array of Struct objects (or instances of the class specified in
|
|
1349
1380
|
record_class), each one representing a record that satisfied the selection
|
1350
1381
|
criteria.</p>
|
1351
1382
|
<p>Since each item in KBResultSet is a Struct object, you can easily reference
|
1352
|
-
|
1383
|
+
its members using field names. So, to print the name and speed of each
|
1353
1384
|
German plane in the table you would code:</p>
|
1354
1385
|
<div class="listingblock">
|
1355
1386
|
<div class="content">
|
@@ -1379,7 +1410,7 @@ the result set, and sort the result set by country (ascending) then speed
|
|
1379
1410
|
</div></div>
|
1380
1411
|
<p>You can also explicitly specify that a field be sorted in ascending order by
|
1381
1412
|
putting a plus sign in front of it. This is not necessary, since ascending
|
1382
|
-
is the default, but
|
1413
|
+
is the default, but its there if you prefer to use it.</p>
|
1383
1414
|
<h3><a id="to-report"></a>Producing a report from a result set</h3>
|
1384
1415
|
<p>Additionally, you can also transform the KBResultSet into a nicely formatted
|
1385
1416
|
report, suitable for printing, by calling KBResultSet#to_report. To print
|
@@ -1486,7 +1517,7 @@ block that indicates which records are to be updated. Additionally, you must
|
|
1486
1517
|
specify the fields to be updated and the new values for those fields.</p>
|
1487
1518
|
<p>You can update records using a Hash, Struct, an Array, or an instance of a
|
1488
1519
|
class you defined. For example, to change the P-51's speed to 405mph and
|
1489
|
-
|
1520
|
+
its range to 1210 miles, you could code:</p>
|
1490
1521
|
<div class="listingblock">
|
1491
1522
|
<div class="content">
|
1492
1523
|
<pre><tt>plane_tbl.update(:speed=>405, :range=>1210) { |r| r.name == 'P-51' }</tt></pre>
|
@@ -1769,7 +1800,7 @@ and the keys as recno's. You can update it using a Hash, Array, or Struct.</p>
|
|
1769
1800
|
<div class="content">
|
1770
1801
|
<pre><tt>plane_tbl[5]</tt></pre>
|
1771
1802
|
</div></div>
|
1772
|
-
<p>You can quickly select an individual record, by passing
|
1803
|
+
<p>You can quickly select an individual record, by passing its recno to a
|
1773
1804
|
table as if it were a hash.</p>
|
1774
1805
|
<p>Returns a single record either in the form of a Struct or an instance of
|
1775
1806
|
the table's custom record class, if specified.</p>
|
@@ -2139,7 +2170,7 @@ incremented. You can use this field in a maintenance script so that the
|
|
2139
2170
|
table is packed whenever the deleted-records counter reaches, say, 5,000
|
2140
2171
|
records.</p>
|
2141
2172
|
<p>The third field in the header is the record_class field. If you specified a
|
2142
|
-
class when you created the table,
|
2173
|
+
class when you created the table, its name will show up here and records
|
2143
2174
|
returned from a #select will be instances of this class. The default is
|
2144
2175
|
"Struct".</p>
|
2145
2176
|
<p>The fourth field in the header is the :recno field. This field is
|
@@ -2157,7 +2188,7 @@ characters delimit records.</p>
|
|
2157
2188
|
<div class="sectionbody">
|
2158
2189
|
<p>There is a server script included in the distribution called kbserver.rb.
|
2159
2190
|
This script uses DRb to turn KirbyBase into a client/server, multi-user
|
2160
|
-
|
2191
|
+
DBMS. This DBMS server handles table locking for you so you don't have to
|
2161
2192
|
worry about it.</p>
|
2162
2193
|
</div>
|
2163
2194
|
<h2><a id="tips-on-improving-performance"></a>Tips on improving performance</h2>
|
@@ -2182,7 +2213,7 @@ result in the same result set being returned:</p>
|
|
2182
2213
|
</div></div>
|
2183
2214
|
<h3>Create indexes on large tables</h3>
|
2184
2215
|
<p>The larger a table becomes, the more sense it makes to create an index on
|
2185
|
-
one or more of
|
2216
|
+
one or more of its fields. Even though you take a hit when KirbyBase first
|
2186
2217
|
initializes because it has to create the index arrays, you make up for it
|
2187
2218
|
after that in greatly improved query speeds. I have noticed speed-ups of
|
2188
2219
|
as much as 10x on large tables.</p>
|
@@ -2199,22 +2230,22 @@ This is even faster than selecting on a regularly indexed field, because the
|
|
2199
2230
|
:recno index that KirbyBase creates for each table is actually a Hash, not
|
2200
2231
|
an Array like all of the regular indexes. So selects are even faster.</p>
|
2201
2232
|
</div>
|
2202
|
-
<h2><a id="
|
2233
|
+
<h2><a id="single-user-diagram"></a>Single-user memory space diagram</h2>
|
2203
2234
|
<div class="sectionbody">
|
2204
2235
|
<div class="imageblock">
|
2205
2236
|
<div class="content">
|
2206
|
-
<img src="images/
|
2237
|
+
<img src="images/single_user.png" alt="images/single_user.png"/>
|
2207
2238
|
</div>
|
2208
|
-
<div class="image-title">Figure:
|
2239
|
+
<div class="image-title">Figure: Single-user (embedded) mode.</div>
|
2209
2240
|
</div>
|
2210
2241
|
</div>
|
2211
|
-
<h2><a id="
|
2242
|
+
<h2><a id="client-server-diagram"></a>Client/Server memory space diagram</h2>
|
2212
2243
|
<div class="sectionbody">
|
2213
2244
|
<div class="imageblock">
|
2214
2245
|
<div class="content">
|
2215
|
-
<img src="images/
|
2246
|
+
<img src="images/client_server.png" alt="images/client_server.png"/>
|
2216
2247
|
</div>
|
2217
|
-
<div class="image-title">Figure:
|
2248
|
+
<div class="image-title">Figure: Client/Server (separate memory spaces) mode.</div>
|
2218
2249
|
</div>
|
2219
2250
|
</div>
|
2220
2251
|
<h2><a id="license"></a>License</h2>
|
@@ -2235,8 +2266,7 @@ an Array like all of the regular indexes. So selects are even faster.</p>
|
|
2235
2266
|
</div>
|
2236
2267
|
<div id="footer">
|
2237
2268
|
<div id="footer-text">
|
2238
|
-
|
2239
|
-
Last updated 01-Dec-2005 13:28:12 Eastern Daylight Time
|
2269
|
+
Last updated 28-Dec-2005 13:27:25 Eastern Daylight Time
|
2240
2270
|
</div>
|
2241
2271
|
</div>
|
2242
2272
|
</body>
|
data/lib/kirbybase.rb
CHANGED
@@ -128,11 +128,215 @@ require 'yaml'
|
|
128
128
|
# speeds up database initialization at the cost of slower table
|
129
129
|
# initialization later.
|
130
130
|
#
|
131
|
+
# 2005-12-28:: Version 2.5.1
|
132
|
+
# * Fixed a bug that had broken encrypted tables.
|
133
|
+
# * Changed KBTable#pack method so that it raises an error if trying to
|
134
|
+
# execute when :connect_type==:client.
|
135
|
+
# * Fixed a bug where it was possible to insert records missing a required
|
136
|
+
# field if using a hash. Thanks to Adam Shelly for this.
|
137
|
+
# * Fixed a bug that occurred when you tried to update records using a
|
138
|
+
# block and you tried to reference a field in the current record inside
|
139
|
+
# the block. Much thanks to Assaph Mehr for reporting this.
|
140
|
+
# * Fixed a bug that allowed you to have duplicate column names. Thanks to
|
141
|
+
# Assaph Mehr for spotting this.
|
142
|
+
# * Changed the way KBTable#set works with memo/blob fields.
|
143
|
+
# * Started creating unit tests.
|
144
|
+
# * Changed the KBTable#clear method to return number of records deleted.
|
145
|
+
# Thanks to Assaph Mehr for this enhancement.
|
146
|
+
# * Moved #build_header_string from KBEngine class to KirbyBase class.
|
147
|
+
# * Added KirbyBase::VERSION constant.
|
148
|
+
#
|
149
|
+
#
|
150
|
+
#---------------------------------------------------------------------------
|
151
|
+
# KBTypeConversionsMixin
|
152
|
+
#---------------------------------------------------------------------------
|
153
|
+
module KBTypeConversionsMixin
|
154
|
+
# Regular expression used to determine if field needs to be un-encoded.
|
155
|
+
UNENCODE_RE = /&(?:amp|linefeed|carriage_return|substitute|pipe);/
|
156
|
+
|
157
|
+
# Regular expression used to determine if field needs to be encoded.
|
158
|
+
ENCODE_RE = /&|\n|\r|\032|\|/
|
159
|
+
|
160
|
+
#-----------------------------------------------------------------------
|
161
|
+
# convert_to_native_type
|
162
|
+
#-----------------------------------------------------------------------
|
163
|
+
#++
|
164
|
+
# Return value converted from storage string to native field type.
|
165
|
+
#
|
166
|
+
def convert_to_native_type(data_type, s)
|
167
|
+
return nil if s.empty? or s.nil?
|
168
|
+
|
169
|
+
case data_type
|
170
|
+
when :String
|
171
|
+
if s =~ UNENCODE_RE
|
172
|
+
return s.gsub('&linefeed;', "\n").gsub('&carriage_return;',
|
173
|
+
"\r").gsub('&substitute;', "\032").gsub('&pipe;', "|"
|
174
|
+
).gsub('&', "&")
|
175
|
+
else
|
176
|
+
return s
|
177
|
+
end
|
178
|
+
when :Integer
|
179
|
+
return s.to_i
|
180
|
+
when :Float
|
181
|
+
return s.to_f
|
182
|
+
when :Boolean
|
183
|
+
if ['false', 'False', nil, false].include?(s)
|
184
|
+
return false
|
185
|
+
else
|
186
|
+
return true
|
187
|
+
end
|
188
|
+
when :Time
|
189
|
+
return Time.parse(s)
|
190
|
+
when :Date
|
191
|
+
return Date.parse(s)
|
192
|
+
when :DateTime
|
193
|
+
return DateTime.parse(s)
|
194
|
+
when :YAML
|
195
|
+
# This code is here in case the YAML field is the last
|
196
|
+
# field in the record. Because YAML normally defines a
|
197
|
+
# nil value as "--- ", but KirbyBase strips trailing
|
198
|
+
# spaces off the end of the record, so if this is the
|
199
|
+
# last field in the record, KirbyBase will strip the
|
200
|
+
# trailing space off and make it "---". When KirbyBase
|
201
|
+
# attempts to convert this value back using to_yaml,
|
202
|
+
# you get an exception.
|
203
|
+
if s == "---"
|
204
|
+
return nil
|
205
|
+
elsif s =~ UNENCODE_RE
|
206
|
+
y = s.gsub('&linefeed;', "\n").gsub('&carriage_return;',
|
207
|
+
"\r").gsub('&substitute;', "\032").gsub('&pipe;', "|"
|
208
|
+
).gsub('&', "&")
|
209
|
+
return YAML.load(y)
|
210
|
+
else
|
211
|
+
return YAML.load(s)
|
212
|
+
end
|
213
|
+
when :Memo
|
214
|
+
memo = KBMemo.new(@tbl.db, s)
|
215
|
+
memo.read_from_file
|
216
|
+
return memo
|
217
|
+
when :Blob
|
218
|
+
blob = KBBlob.new(@tbl.db, s)
|
219
|
+
blob.read_from_file
|
220
|
+
return blob
|
221
|
+
else
|
222
|
+
raise "Invalid field type: %s" % data_type
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
#-----------------------------------------------------------------------
|
227
|
+
# convert_to_encoded_string
|
228
|
+
#-----------------------------------------------------------------------
|
229
|
+
#++
|
230
|
+
# Return value converted to encoded String object suitable for storage.
|
231
|
+
#
|
232
|
+
def convert_to_encoded_string(data_type, value)
|
233
|
+
case data_type
|
234
|
+
when :YAML
|
235
|
+
y = value.to_yaml
|
236
|
+
if y =~ ENCODE_RE
|
237
|
+
return y.gsub("&", '&').gsub("\n", '&linefeed;').gsub(
|
238
|
+
"\r", '&carriage_return;').gsub("\032", '&substitute;'
|
239
|
+
).gsub("|", '&pipe;')
|
240
|
+
else
|
241
|
+
return y
|
242
|
+
end
|
243
|
+
when :String
|
244
|
+
if value =~ ENCODE_RE
|
245
|
+
return value.gsub("&", '&').gsub("\n", '&linefeed;'
|
246
|
+
).gsub("\r", '&carriage_return;').gsub("\032",
|
247
|
+
'&substitute;').gsub("|", '&pipe;')
|
248
|
+
else
|
249
|
+
return value
|
250
|
+
end
|
251
|
+
when :Memo
|
252
|
+
return value.filepath
|
253
|
+
when :Blob
|
254
|
+
return value.filepath
|
255
|
+
else
|
256
|
+
return value.to_s
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
#---------------------------------------------------------------------------
|
263
|
+
# KBEncryptionMixin
|
264
|
+
#---------------------------------------------------------------------------
|
265
|
+
module KBEncryptionMixin
|
266
|
+
EN_STR = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + \
|
267
|
+
'0123456789.+-,$:|&;_ '
|
268
|
+
EN_STR_LEN = EN_STR.length
|
269
|
+
EN_KEY1 = ")2VER8GE\"87-E\n" #*** DO NOT CHANGE ***
|
270
|
+
EN_KEY = EN_KEY1.unpack("u")[0]
|
271
|
+
EN_KEY_LEN = EN_KEY.length
|
272
|
+
|
273
|
+
|
274
|
+
#-----------------------------------------------------------------------
|
275
|
+
# encrypt_str
|
276
|
+
#-----------------------------------------------------------------------
|
277
|
+
#++
|
278
|
+
# Returns an encrypted string, using the Vignere Cipher.
|
279
|
+
#
|
280
|
+
def encrypt_str(s)
|
281
|
+
new_str = ''
|
282
|
+
i_key = -1
|
283
|
+
s.each_byte do |c|
|
284
|
+
if i_key < EN_KEY_LEN - 1
|
285
|
+
i_key += 1
|
286
|
+
else
|
287
|
+
i_key = 0
|
288
|
+
end
|
289
|
+
|
290
|
+
if EN_STR.index(c.chr).nil?
|
291
|
+
new_str << c.chr
|
292
|
+
next
|
293
|
+
end
|
294
|
+
|
295
|
+
i_from_str = EN_STR.index(EN_KEY[i_key]) + EN_STR.index(c.chr)
|
296
|
+
i_from_str = i_from_str - EN_STR_LEN if i_from_str >= EN_STR_LEN
|
297
|
+
new_str << EN_STR[i_from_str]
|
298
|
+
end
|
299
|
+
return new_str
|
300
|
+
end
|
301
|
+
|
302
|
+
#-----------------------------------------------------------------------
|
303
|
+
# unencrypt_str
|
304
|
+
#-----------------------------------------------------------------------
|
305
|
+
#++
|
306
|
+
# Returns an unencrypted string, using the Vignere Cipher.
|
307
|
+
#
|
308
|
+
def unencrypt_str(s)
|
309
|
+
new_str = ''
|
310
|
+
i_key = -1
|
311
|
+
s.each_byte do |c|
|
312
|
+
if i_key < EN_KEY_LEN - 1
|
313
|
+
i_key += 1
|
314
|
+
else
|
315
|
+
i_key = 0
|
316
|
+
end
|
317
|
+
|
318
|
+
if EN_STR.index(c.chr).nil?
|
319
|
+
new_str << c.chr
|
320
|
+
next
|
321
|
+
end
|
322
|
+
|
323
|
+
i_from_str = EN_STR.index(c.chr) - EN_STR.index(EN_KEY[i_key])
|
324
|
+
i_from_str = i_from_str + EN_STR_LEN if i_from_str < 0
|
325
|
+
new_str << EN_STR[i_from_str]
|
326
|
+
end
|
327
|
+
return new_str
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
|
131
332
|
#---------------------------------------------------------------------------
|
132
333
|
# KirbyBase
|
133
334
|
#---------------------------------------------------------------------------
|
134
335
|
class KirbyBase
|
135
336
|
include DRb::DRbUndumped
|
337
|
+
include KBTypeConversionsMixin
|
338
|
+
|
339
|
+
VERSION = "2.5.1"
|
136
340
|
|
137
341
|
attr_reader :engine
|
138
342
|
|
@@ -155,7 +359,9 @@ class KirbyBase
|
|
155
359
|
# *ext*:: String specifying extension of table files.
|
156
360
|
# *memo_blob_path*:: String specifying path to location of memo/blob
|
157
361
|
# files.
|
158
|
-
#
|
362
|
+
# *delay_index_creation*:: Boolean specifying whether to delay index
|
363
|
+
# creation for each table until that table is
|
364
|
+
# requested by user.
|
159
365
|
def initialize(connect_type=:local, host=nil, port=nil, path='./',
|
160
366
|
ext='.tbl', memo_blob_path='./', delay_index_creation=false)
|
161
367
|
@connect_type = connect_type
|
@@ -210,29 +416,12 @@ class KirbyBase
|
|
210
416
|
# happens when the table instance is first created, I go ahead and
|
211
417
|
# create table instances right off the bat.
|
212
418
|
#
|
213
|
-
# Also, I
|
214
|
-
#
|
215
|
-
#
|
216
|
-
# instances, since there was no need for the server-side KirbyBase
|
217
|
-
# instance to create table instances. But, since I want indexes
|
218
|
-
# created at db initialization and the server's db instance might
|
219
|
-
# be initialized long before any client's db is initialized, I now
|
220
|
-
# let the server create table instances also. This is strictly to
|
221
|
-
# get the indexes created, there is no other use for the table
|
222
|
-
# instances on the server side as they will never be used.
|
419
|
+
# Also, I let the server create table instances also. This is
|
420
|
+
# strictly to get the indexes created, there is no other use for
|
421
|
+
# the table instances on the server side as they will never be used.
|
223
422
|
# Everything should and does go through the table instances created
|
224
|
-
# on the client-side.
|
225
|
-
#
|
226
|
-
# Ok, I added back in a conditional flag that allows me to turn off
|
227
|
-
# index initialization if this is a server. The reason I added this
|
228
|
-
# back in was that I was running into a problem when running a
|
229
|
-
# KirbyBase server as a win32 service. When I tried to start the
|
230
|
-
# service, it kept bombing out saying that the application had not
|
231
|
-
# responded in a timely manner. It appears to be because it was
|
232
|
-
# taking a few seconds to build the indexes when it initialized.
|
233
|
-
# When I deleted the index from the table, the service would start
|
234
|
-
# just fine. I need to find out if I can set a timeout parameter
|
235
|
-
# when starting the win32 service.
|
423
|
+
# on the client-side. You can turn this off by specifying true for
|
424
|
+
# the delay_index_creation argument.
|
236
425
|
if server? and @delay_index_creation
|
237
426
|
else
|
238
427
|
@engine.tables.each do |tbl|
|
@@ -336,12 +525,133 @@ class KirbyBase
|
|
336
525
|
raise "No table name specified!" if t.name.nil?
|
337
526
|
raise "No table field definitions specified!" if t.field_defs.nil?
|
338
527
|
|
339
|
-
|
528
|
+
# Can't create a table that already exists!
|
529
|
+
raise "Table already exists!" if table_exists?(t.name)
|
530
|
+
|
531
|
+
raise 'Must have a field type for each field name' \
|
532
|
+
unless t.field_defs.size.remainder(2) == 0
|
533
|
+
|
534
|
+
# Check to make sure there are no duplicate field names.
|
535
|
+
temp_field_names = []
|
536
|
+
(0...t.field_defs.size).step(2) do |x|
|
537
|
+
temp_field_names << t.field_defs[x]
|
538
|
+
end
|
539
|
+
raise 'Duplicate field names are not allowed!' unless \
|
540
|
+
temp_field_names == temp_field_names.uniq
|
541
|
+
|
542
|
+
temp_field_defs = []
|
543
|
+
(0...t.field_defs.size).step(2) do |x|
|
544
|
+
temp_field_defs << build_header_field_string(t.field_defs[x],
|
545
|
+
t.field_defs[x+1])
|
546
|
+
end
|
547
|
+
|
548
|
+
@engine.new_table(t.name, temp_field_defs, t.encrypt,
|
340
549
|
t.record_class.to_s)
|
341
550
|
|
342
551
|
return get_table(t.name)
|
343
552
|
end
|
344
553
|
|
554
|
+
#-----------------------------------------------------------------------
|
555
|
+
# build_header_field_string
|
556
|
+
#-----------------------------------------------------------------------
|
557
|
+
def build_header_field_string(field_name_def, field_type_def)
|
558
|
+
# Put field name at start of string definition.
|
559
|
+
temp_field_def = field_name_def.to_s + ':'
|
560
|
+
|
561
|
+
# If field type is a hash, that means that it is not just a
|
562
|
+
# simple field. Either is is a key field, it is being used in an
|
563
|
+
# index, it is a default value, it is a required field, it is a
|
564
|
+
# Lookup field, it is a Link_many field, or it is a Calculated
|
565
|
+
# field. This next bit of code is to piece together a proper
|
566
|
+
# string so that it can be written out to the header rec.
|
567
|
+
if field_type_def.is_a?(Hash)
|
568
|
+
raise 'Missing :DataType key in field_type hash!' unless \
|
569
|
+
field_type_def.has_key?(:DataType)
|
570
|
+
|
571
|
+
temp_type = field_type_def[:DataType]
|
572
|
+
|
573
|
+
raise 'Invalid field type: %s' % temp_type unless \
|
574
|
+
KBTable.valid_field_type?(temp_type)
|
575
|
+
|
576
|
+
temp_field_def += field_type_def[:DataType].to_s
|
577
|
+
|
578
|
+
# Check if this field is a key for the table.
|
579
|
+
if field_type_def.has_key?(:Key)
|
580
|
+
temp_field_def += ':Key->true'
|
581
|
+
end
|
582
|
+
|
583
|
+
# Check for Index definition.
|
584
|
+
if field_type_def.has_key?(:Index)
|
585
|
+
raise 'Invalid field type for index: %s' % temp_type \
|
586
|
+
unless KBTable.valid_index_type?(temp_type)
|
587
|
+
|
588
|
+
temp_field_def += ':Index->' + field_type_def[:Index].to_s
|
589
|
+
end
|
590
|
+
|
591
|
+
# Check for Default value definition.
|
592
|
+
if field_type_def.has_key?(:Default)
|
593
|
+
raise 'Cannot set default value for this type: ' + \
|
594
|
+
'%s' % temp_type unless KBTable.valid_default_type?(
|
595
|
+
temp_type)
|
596
|
+
|
597
|
+
unless field_type_def[:Default].nil?
|
598
|
+
raise 'Invalid default value ' + \
|
599
|
+
'%s for column %s' % [field_type_def[:Default],
|
600
|
+
field_name_def] unless KBTable.valid_data_type?(
|
601
|
+
temp_type, field_type_def[:Default])
|
602
|
+
|
603
|
+
temp_field_def += ':Default->' + \
|
604
|
+
convert_to_encoded_string(temp_type,
|
605
|
+
field_type_def[:Default])
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# Check for Required definition.
|
610
|
+
if field_type_def.has_key?(:Required)
|
611
|
+
raise 'Required must be true or false!' unless \
|
612
|
+
[true, false].include?(field_type_def[:Required])
|
613
|
+
|
614
|
+
temp_field_def += \
|
615
|
+
':Required->%s' % field_type_def[:Required]
|
616
|
+
end
|
617
|
+
|
618
|
+
# Check for Lookup field, Link_many field, Calculated field
|
619
|
+
# definition.
|
620
|
+
if field_type_def.has_key?(:Lookup)
|
621
|
+
if field_type_def[:Lookup].is_a?(Array)
|
622
|
+
temp_field_def += \
|
623
|
+
':Lookup->%s.%s' % field_type_def[:Lookup]
|
624
|
+
else
|
625
|
+
tbl = get_table(field_type_def[:Lookup])
|
626
|
+
temp_field_def += \
|
627
|
+
':Lookup->%s.%s' % [field_type_def[:Lookup],
|
628
|
+
tbl.lookup_key]
|
629
|
+
end
|
630
|
+
elsif field_type_def.has_key?(:Link_many)
|
631
|
+
raise 'Field type for Link_many field must be :ResultSet' \
|
632
|
+
unless temp_type == :ResultSet
|
633
|
+
temp_field_def += \
|
634
|
+
':Link_many->%s=%s.%s' % field_type_def[:Link_many]
|
635
|
+
elsif field_type_def.has_key?(:Calculated)
|
636
|
+
temp_field_def += \
|
637
|
+
':Calculated->%s' % field_type_def[:Calculated]
|
638
|
+
end
|
639
|
+
else
|
640
|
+
if KBTable.valid_field_type?(field_type_def)
|
641
|
+
temp_field_def += field_type_def.to_s
|
642
|
+
elsif table_exists?(field_type_def)
|
643
|
+
tbl = get_table(field_type_def)
|
644
|
+
temp_field_def += \
|
645
|
+
'%s:Lookup->%s.%s' % [tbl.field_types[
|
646
|
+
tbl.field_names.index(tbl.lookup_key)], field_type_def,
|
647
|
+
tbl.lookup_key]
|
648
|
+
else
|
649
|
+
raise 'Invalid field type: %s' % field_type_def
|
650
|
+
end
|
651
|
+
end
|
652
|
+
return temp_field_def
|
653
|
+
end
|
654
|
+
|
345
655
|
#-----------------------------------------------------------------------
|
346
656
|
# rename_table
|
347
657
|
#-----------------------------------------------------------------------
|
@@ -352,12 +662,14 @@ class KirbyBase
|
|
352
662
|
# *new_tablename*:: Symbol of new table name.
|
353
663
|
#
|
354
664
|
def rename_table(old_tablename, new_tablename)
|
665
|
+
raise "Cannot rename table running in client mode!" if client?
|
355
666
|
raise "Table does not exist!" unless table_exists?(old_tablename)
|
356
667
|
raise(ArgumentError, 'Existing table name must be a symbol!') \
|
357
668
|
unless old_tablename.is_a?(Symbol)
|
358
669
|
raise(ArgumentError, 'New table name must be a symbol!') unless \
|
359
670
|
new_tablename.is_a?(Symbol)
|
360
671
|
raise "Table already exists!" if table_exists?(new_tablename)
|
672
|
+
|
361
673
|
@table_hash.delete(old_tablename)
|
362
674
|
@engine.rename_table(old_tablename, new_tablename)
|
363
675
|
get_table(new_tablename)
|
@@ -376,6 +688,7 @@ class KirbyBase
|
|
376
688
|
tablename.is_a?(Symbol)
|
377
689
|
raise "Table does not exist!" unless table_exists?(tablename)
|
378
690
|
@table_hash.delete(tablename)
|
691
|
+
|
379
692
|
return @engine.delete_table(tablename)
|
380
693
|
end
|
381
694
|
|
@@ -397,90 +710,13 @@ class KirbyBase
|
|
397
710
|
end
|
398
711
|
|
399
712
|
|
400
|
-
#---------------------------------------------------------------------------
|
401
|
-
# KBTypeConversionsMixin
|
402
|
-
#---------------------------------------------------------------------------
|
403
|
-
module KBTypeConversionsMixin
|
404
|
-
UNENCODE_RE = /&(?:amp|linefeed|carriage_return|substitute|pipe);/
|
405
|
-
|
406
|
-
#-----------------------------------------------------------------------
|
407
|
-
# convert_to
|
408
|
-
#-----------------------------------------------------------------------
|
409
|
-
def convert_to(data_type, s)
|
410
|
-
return nil if s.empty? or s.nil?
|
411
|
-
|
412
|
-
case data_type
|
413
|
-
when :String
|
414
|
-
if s =~ UNENCODE_RE
|
415
|
-
return s.gsub('&linefeed;', "\n").gsub('&carriage_return;',
|
416
|
-
"\r").gsub('&substitute;', "\032").gsub('&pipe;', "|"
|
417
|
-
).gsub('&', "&")
|
418
|
-
else
|
419
|
-
return s
|
420
|
-
end
|
421
|
-
when :Integer
|
422
|
-
return s.to_i
|
423
|
-
when :Float
|
424
|
-
return s.to_f
|
425
|
-
when :Boolean
|
426
|
-
if ['false', 'False', nil, false].include?(s)
|
427
|
-
return false
|
428
|
-
else
|
429
|
-
return true
|
430
|
-
end
|
431
|
-
when :Time
|
432
|
-
return Time.parse(s)
|
433
|
-
when :Date
|
434
|
-
return Date.parse(s)
|
435
|
-
when :DateTime
|
436
|
-
return DateTime.parse(s)
|
437
|
-
when :YAML
|
438
|
-
# This code is here in case the YAML field is the last
|
439
|
-
# field in the record. Because YAML normall defines a
|
440
|
-
# nil value as "--- ", but KirbyBase strips trailing
|
441
|
-
# spaces off the end of the record, so if this is the
|
442
|
-
# last field in the record, KirbyBase will strip the
|
443
|
-
# trailing space off and make it "---". When KirbyBase
|
444
|
-
# attempts to convert this value back using to_yaml,
|
445
|
-
# you get an exception.
|
446
|
-
if s == "---"
|
447
|
-
return nil
|
448
|
-
elsif s =~ UNENCODE_RE
|
449
|
-
y = s.gsub('&linefeed;', "\n").gsub('&carriage_return;',
|
450
|
-
"\r").gsub('&substitute;', "\032").gsub('&pipe;', "|"
|
451
|
-
).gsub('&', "&")
|
452
|
-
return YAML.load(y)
|
453
|
-
else
|
454
|
-
return YAML.load(s)
|
455
|
-
end
|
456
|
-
when :Memo
|
457
|
-
memo = KBMemo.new(@tbl.db, s)
|
458
|
-
memo.read_from_file
|
459
|
-
return memo
|
460
|
-
when :Blob
|
461
|
-
blob = KBBlob.new(@tbl.db, s)
|
462
|
-
blob.read_from_file
|
463
|
-
return blob
|
464
|
-
else
|
465
|
-
raise "Invalid field type: %s" % data_type
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
|
471
713
|
#---------------------------------------------------------------------------
|
472
714
|
# KBEngine
|
473
715
|
#---------------------------------------------------------------------------
|
474
716
|
class KBEngine
|
475
717
|
include DRb::DRbUndumped
|
476
718
|
include KBTypeConversionsMixin
|
477
|
-
|
478
|
-
EN_STR = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + \
|
479
|
-
'0123456789.+-,$:|&;_ '
|
480
|
-
EN_STR_LEN = EN_STR.length
|
481
|
-
EN_KEY1 = ")2VER8GE\"87-E\n" #*** DO NOT CHANGE ***
|
482
|
-
EN_KEY = EN_KEY1.unpack("u")[0]
|
483
|
-
EN_KEY_LEN = EN_KEY.length
|
719
|
+
include KBEncryptionMixin
|
484
720
|
|
485
721
|
# Make constructor private.
|
486
722
|
private_class_method :new
|
@@ -546,6 +782,13 @@ class KBEngine
|
|
546
782
|
@recno_indexes.include?(table.name)
|
547
783
|
end
|
548
784
|
|
785
|
+
#-----------------------------------------------------------------------
|
786
|
+
# get_recno_index
|
787
|
+
#-----------------------------------------------------------------------
|
788
|
+
def get_recno_index(table)
|
789
|
+
return @recno_indexes[table.name].get_idx
|
790
|
+
end
|
791
|
+
|
549
792
|
#-----------------------------------------------------------------------
|
550
793
|
# init_index
|
551
794
|
#-----------------------------------------------------------------------
|
@@ -633,13 +876,6 @@ class KBEngine
|
|
633
876
|
return @indexes["#{table.name}_#{index_name}".to_sym].get_timestamp
|
634
877
|
end
|
635
878
|
|
636
|
-
#-----------------------------------------------------------------------
|
637
|
-
# get_recno_index
|
638
|
-
#-----------------------------------------------------------------------
|
639
|
-
def get_recno_index(table)
|
640
|
-
return @recno_indexes[table.name].get_idx
|
641
|
-
end
|
642
|
-
|
643
879
|
#-----------------------------------------------------------------------
|
644
880
|
# table_exists?
|
645
881
|
#-----------------------------------------------------------------------
|
@@ -648,107 +884,18 @@ class KBEngine
|
|
648
884
|
end
|
649
885
|
|
650
886
|
#-----------------------------------------------------------------------
|
651
|
-
# tables
|
652
|
-
#-----------------------------------------------------------------------
|
653
|
-
def tables
|
654
|
-
list = []
|
655
|
-
Dir.foreach(@db.path) { |filename|
|
656
|
-
list << File.basename(filename, '.*').to_sym if \
|
657
|
-
File.extname(filename) == @db.ext
|
658
|
-
}
|
659
|
-
return list
|
660
|
-
end
|
661
|
-
|
662
|
-
#-----------------------------------------------------------------------
|
663
|
-
# build_header_field_string
|
664
|
-
#-----------------------------------------------------------------------
|
665
|
-
def build_header_field_string(field_name_def, field_type_def)
|
666
|
-
# Put field name at start of string definition.
|
667
|
-
temp_field_def = field_name_def.to_s + ':'
|
668
|
-
|
669
|
-
# if field type is a hash, that means that it is not just a
|
670
|
-
# simple field. Either is is being used in an index, it is a
|
671
|
-
# Lookup field, it is a Link_many field, or it is a Calculated
|
672
|
-
# field. This next bit of code is to piece together a proper
|
673
|
-
# string so that it can be written out to the header rec.
|
674
|
-
if field_type_def.is_a?(Hash)
|
675
|
-
raise 'Missing :DataType key in field type hash!' unless \
|
676
|
-
field_type_def.has_key?(:DataType)
|
677
|
-
|
678
|
-
temp_type = field_type_def[:DataType]
|
679
|
-
|
680
|
-
raise 'Invalid field type: %s' % temp_type unless \
|
681
|
-
KBTable.valid_field_type?(temp_type)
|
682
|
-
|
683
|
-
temp_field_def += field_type_def[:DataType].to_s
|
684
|
-
|
685
|
-
if field_type_def.has_key?(:Key)
|
686
|
-
temp_field_def += ':Key->true'
|
687
|
-
end
|
688
|
-
|
689
|
-
if field_type_def.has_key?(:Index)
|
690
|
-
raise 'Invalid field type for index: %s' % temp_type \
|
691
|
-
unless KBTable.valid_index_type?(temp_type)
|
692
|
-
|
693
|
-
temp_field_def += ':Index->' + field_type_def[:Index].to_s
|
694
|
-
end
|
695
|
-
|
696
|
-
if field_type_def.has_key?(:Default)
|
697
|
-
raise 'Cannot set default value for this type: ' + \
|
698
|
-
'%s' % temp_type unless KBTable.valid_default_type?(
|
699
|
-
temp_type)
|
700
|
-
|
701
|
-
unless field_type_def[:Default].nil?
|
702
|
-
temp_field_def += ':Default->' + \
|
703
|
-
KBTable.convert_to_string(temp_type,
|
704
|
-
field_type_def[:Default])
|
705
|
-
end
|
706
|
-
end
|
707
|
-
|
708
|
-
if field_type_def.has_key?(:Required)
|
709
|
-
raise 'Required must be true or false!' unless \
|
710
|
-
[true, false].include?(field_type_def[:Required])
|
711
|
-
|
712
|
-
temp_field_def += \
|
713
|
-
':Required->%s' % field_type_def[:Required]
|
714
|
-
end
|
715
|
-
|
716
|
-
if field_type_def.has_key?(:Lookup)
|
717
|
-
if field_type_def[:Lookup].is_a?(Array)
|
718
|
-
temp_field_def += \
|
719
|
-
':Lookup->%s.%s' % field_type_def[:Lookup]
|
720
|
-
else
|
721
|
-
tbl = @db.get_table(field_type_def[:Lookup])
|
722
|
-
temp_field_def += \
|
723
|
-
':Lookup->%s.%s' % [field_type_def[:Lookup],
|
724
|
-
tbl.lookup_key]
|
725
|
-
end
|
726
|
-
elsif field_type_def.has_key?(:Link_many)
|
727
|
-
raise 'Field type for Link_many field must be :ResultSet' \
|
728
|
-
unless temp_type == :ResultSet
|
729
|
-
temp_field_def += \
|
730
|
-
':Link_many->%s=%s.%s' % field_type_def[:Link_many]
|
731
|
-
elsif field_type_def.has_key?(:Calculated)
|
732
|
-
temp_field_def += \
|
733
|
-
':Calculated->%s' % field_type_def[:Calculated]
|
734
|
-
end
|
735
|
-
else
|
736
|
-
if KBTable.valid_field_type?(field_type_def)
|
737
|
-
temp_field_def += field_type_def.to_s
|
738
|
-
elsif @db.table_exists?(field_type_def)
|
739
|
-
tbl = @db.get_table(field_type_def)
|
740
|
-
temp_field_def += \
|
741
|
-
'%s:Lookup->%s.%s' % [tbl.field_types[
|
742
|
-
tbl.field_names.index(tbl.lookup_key)], field_type_def,
|
743
|
-
tbl.lookup_key]
|
744
|
-
else
|
745
|
-
raise 'Invalid field type: %s' % field_type_def
|
746
|
-
end
|
747
|
-
end
|
748
|
-
return temp_field_def
|
749
|
-
end
|
750
|
-
|
751
|
-
#-----------------------------------------------------------------------
|
887
|
+
# tables
|
888
|
+
#-----------------------------------------------------------------------
|
889
|
+
def tables
|
890
|
+
list = []
|
891
|
+
Dir.foreach(@db.path) { |filename|
|
892
|
+
list << File.basename(filename, '.*').to_sym if \
|
893
|
+
File.extname(filename) == @db.ext
|
894
|
+
}
|
895
|
+
return list
|
896
|
+
end
|
897
|
+
|
898
|
+
#-----------------------------------------------------------------------
|
752
899
|
# new_table
|
753
900
|
#-----------------------------------------------------------------------
|
754
901
|
#++
|
@@ -756,22 +903,11 @@ class KBEngine
|
|
756
903
|
# called in your application, but only called by #create_table.
|
757
904
|
#
|
758
905
|
def new_table(name, field_defs, encrypt, record_class)
|
759
|
-
# Can't create a table that already exists!
|
760
|
-
raise "Table already exists!" if table_exists?(name)
|
761
|
-
|
762
|
-
raise 'Must have a field type for each field name' \
|
763
|
-
unless field_defs.size.remainder(2) == 0
|
764
|
-
temp_field_defs = []
|
765
|
-
(0...field_defs.size).step(2) do |x|
|
766
|
-
temp_field_defs << build_header_field_string(field_defs[x],
|
767
|
-
field_defs[x+1])
|
768
|
-
end
|
769
|
-
|
770
906
|
# Header rec consists of last record no. used, delete count, and
|
771
907
|
# all field names/types. Here, I am inserting the 'recno' field
|
772
908
|
# at the beginning of the fields.
|
773
909
|
header_rec = ['000000', '000000', record_class, 'recno:Integer',
|
774
|
-
|
910
|
+
field_defs].join('|')
|
775
911
|
|
776
912
|
header_rec = 'Z' + encrypt_str(header_rec) if encrypt
|
777
913
|
|
@@ -788,9 +924,9 @@ class KBEngine
|
|
788
924
|
#-----------------------------------------------------------------------
|
789
925
|
def delete_table(tablename)
|
790
926
|
with_write_lock(tablename) do
|
927
|
+
File.delete(File.join(@db.path, tablename.to_s + @db.ext))
|
791
928
|
remove_indexes(tablename)
|
792
929
|
remove_recno_index(tablename)
|
793
|
-
File.delete(File.join(@db.path, tablename.to_s + @db.ext))
|
794
930
|
return true
|
795
931
|
end
|
796
932
|
end
|
@@ -807,8 +943,8 @@ class KBEngine
|
|
807
943
|
#-----------------------------------------------------------------------
|
808
944
|
def reset_recno_ctr(table)
|
809
945
|
with_write_locked_table(table) do |fptr|
|
810
|
-
|
811
|
-
|
946
|
+
encrypted, header_line = get_header_record(table, fptr)
|
947
|
+
last_rec_no, rest_of_line = header_line.split('|', 2)
|
812
948
|
write_header_record(table, fptr,
|
813
949
|
['%06d' % 0, rest_of_line].join('|'))
|
814
950
|
return true
|
@@ -820,7 +956,7 @@ class KBEngine
|
|
820
956
|
#-----------------------------------------------------------------------
|
821
957
|
def get_header_vars(table)
|
822
958
|
with_table(table) do |fptr|
|
823
|
-
line = get_header_record(table, fptr)
|
959
|
+
encrypted, line = get_header_record(table, fptr)
|
824
960
|
|
825
961
|
last_rec_no, del_ctr, record_class, *flds = line.split('|')
|
826
962
|
field_names = flds.collect { |x| x.split(':')[0].to_sym }
|
@@ -838,10 +974,12 @@ class KBEngine
|
|
838
974
|
field_indexes[i] = y
|
839
975
|
elsif y =~ /Default/
|
840
976
|
field_defaults[i] = \
|
841
|
-
|
977
|
+
convert_to_native_type(field_types[i],
|
978
|
+
y.split('->')[1])
|
842
979
|
elsif y =~ /Required/
|
843
980
|
field_requireds[i] = \
|
844
|
-
|
981
|
+
convert_to_native_type(:Boolean,
|
982
|
+
y.split('->')[1])
|
845
983
|
else
|
846
984
|
field_extras[i][y.split('->')[0]] = \
|
847
985
|
y.split('->')[1]
|
@@ -849,7 +987,7 @@ class KBEngine
|
|
849
987
|
end
|
850
988
|
end
|
851
989
|
end
|
852
|
-
return [
|
990
|
+
return [encrypted, last_rec_no.to_i, del_ctr.to_i,
|
853
991
|
record_class, field_names, field_types, field_indexes,
|
854
992
|
field_defaults, field_requireds, field_extras]
|
855
993
|
end
|
@@ -869,21 +1007,12 @@ class KBEngine
|
|
869
1007
|
|
870
1008
|
# Loop through table.
|
871
1009
|
while true
|
872
|
-
# Record current position in table.
|
873
|
-
# detail record.
|
1010
|
+
# Record current position in table.
|
874
1011
|
fpos = fptr.tell
|
875
|
-
|
876
|
-
line.chomp!
|
877
|
-
line_length = line.length
|
878
|
-
|
879
|
-
line = unencrypt_str(line) if encrypted
|
880
|
-
line.strip!
|
1012
|
+
rec, line_length = line_to_rec(fptr.readline, encrypted)
|
881
1013
|
|
882
|
-
|
883
|
-
next if line == ''
|
1014
|
+
next if rec.empty?
|
884
1015
|
|
885
|
-
# Split the line up into fields.
|
886
|
-
rec = line.split('|', -1)
|
887
1016
|
rec << fpos << line_length
|
888
1017
|
recs << rec
|
889
1018
|
end
|
@@ -912,18 +1041,10 @@ class KBEngine
|
|
912
1041
|
# position order, which should be fastest.
|
913
1042
|
recnos.collect { |r| [recno_idx[r], r] }.sort.each do |r|
|
914
1043
|
fptr.seek(r[0])
|
915
|
-
|
916
|
-
line.chomp!
|
917
|
-
line_length = line.length
|
918
|
-
|
919
|
-
line = unencrypt_str(line) if encrypted
|
920
|
-
line.strip!
|
1044
|
+
rec, line_length = line_to_rec(fptr.readline, encrypted)
|
921
1045
|
|
922
|
-
|
923
|
-
next if line == ''
|
1046
|
+
next if rec.empty?
|
924
1047
|
|
925
|
-
# Split the line up into fields.
|
926
|
-
rec = line.split('|', -1)
|
927
1048
|
raise "Index Corrupt!" unless rec[0].to_i == r[1]
|
928
1049
|
rec << r[0] << line_length
|
929
1050
|
recs << rec
|
@@ -943,24 +1064,32 @@ class KBEngine
|
|
943
1064
|
|
944
1065
|
with_table(table) do |fptr|
|
945
1066
|
fptr.seek(recno_idx[recno])
|
946
|
-
|
947
|
-
line.chomp!
|
948
|
-
line_length = line.length
|
949
|
-
|
950
|
-
line = unencrypt_str(line) if encrypted
|
951
|
-
line.strip!
|
1067
|
+
rec, line_length = line_to_rec(fptr.readline, encrypted)
|
952
1068
|
|
953
|
-
|
1069
|
+
raise "Recno Index Corrupt for table %s!" % table.name if \
|
1070
|
+
rec.empty?
|
954
1071
|
|
955
|
-
|
956
|
-
|
1072
|
+
raise "Recno Index Corrupt for table %s!" % table.name unless \
|
1073
|
+
rec[0].to_i == recno
|
957
1074
|
|
958
|
-
raise "Index Corrupt!" unless rec[0].to_i == recno
|
959
1075
|
rec << recno_idx[recno] << line_length
|
960
1076
|
return rec
|
961
1077
|
end
|
962
1078
|
end
|
963
1079
|
|
1080
|
+
#-----------------------------------------------------------------------
|
1081
|
+
# line_to_rec
|
1082
|
+
#-----------------------------------------------------------------------
|
1083
|
+
def line_to_rec(line, encrypted)
|
1084
|
+
line.chomp!
|
1085
|
+
line_length = line.length
|
1086
|
+
line = unencrypt_str(line) if encrypted
|
1087
|
+
line.strip!
|
1088
|
+
|
1089
|
+
# Convert line to rec and return rec and line length.
|
1090
|
+
return line.split('|', -1), line_length
|
1091
|
+
end
|
1092
|
+
|
964
1093
|
#-----------------------------------------------------------------------
|
965
1094
|
# insert_record
|
966
1095
|
#-----------------------------------------------------------------------
|
@@ -1130,17 +1259,13 @@ class KBEngine
|
|
1130
1259
|
#-----------------------------------------------------------------------
|
1131
1260
|
# add_column
|
1132
1261
|
#-----------------------------------------------------------------------
|
1133
|
-
def add_column(table,
|
1134
|
-
|
1135
|
-
|
1136
|
-
if after.nil?
|
1262
|
+
def add_column(table, field_def, after)
|
1263
|
+
# Find the index position of where to insert the column, either at
|
1264
|
+
# the end (-1) or after the field specified.
|
1265
|
+
if after.nil? or table.field_names.last == after
|
1137
1266
|
insert_after = -1
|
1138
1267
|
else
|
1139
|
-
|
1140
|
-
insert_after = -1
|
1141
|
-
else
|
1142
|
-
insert_after = table.field_names.index(after)+1
|
1143
|
-
end
|
1268
|
+
insert_after = table.field_names.index(after)+1
|
1144
1269
|
end
|
1145
1270
|
|
1146
1271
|
with_write_lock(table.name) do
|
@@ -1151,20 +1276,21 @@ class KBEngine
|
|
1151
1276
|
|
1152
1277
|
if line[0..0] == 'Z'
|
1153
1278
|
header_rec = unencrypt_str(line[1..-1]).split('|')
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1279
|
+
else
|
1280
|
+
header_rec = line.split('|')
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
if insert_after == -1
|
1284
|
+
header_rec.insert(insert_after, field_def)
|
1285
|
+
else
|
1286
|
+
# Need to account for recno ctr, delete ctr, record class.
|
1287
|
+
header_rec.insert(insert_after+3, field_def)
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
if line[0..0] == 'Z'
|
1159
1291
|
new_fptr.write('Z' + encrypt_str(header_rec.join('|')) +
|
1160
1292
|
"\n")
|
1161
1293
|
else
|
1162
|
-
header_rec = line.split('|')
|
1163
|
-
if insert_after == -1
|
1164
|
-
header_rec.insert(insert_after, temp_field_def)
|
1165
|
-
else
|
1166
|
-
header_rec.insert(insert_after+3, temp_field_def)
|
1167
|
-
end
|
1168
1294
|
new_fptr.write(header_rec.join('|') + "\n")
|
1169
1295
|
end
|
1170
1296
|
|
@@ -1178,7 +1304,7 @@ class KBEngine
|
|
1178
1304
|
temp_line = line
|
1179
1305
|
end
|
1180
1306
|
|
1181
|
-
rec = temp_line.split('|')
|
1307
|
+
rec = temp_line.split('|', -1)
|
1182
1308
|
rec.insert(insert_after, '')
|
1183
1309
|
|
1184
1310
|
if table.encrypted?
|
@@ -1231,7 +1357,7 @@ class KBEngine
|
|
1231
1357
|
temp_line = line
|
1232
1358
|
end
|
1233
1359
|
|
1234
|
-
rec = temp_line.split('|')
|
1360
|
+
rec = temp_line.split('|', -1)
|
1235
1361
|
rec.delete_at(col_index)
|
1236
1362
|
|
1237
1363
|
if table.encrypted?
|
@@ -1697,10 +1823,12 @@ class KBEngine
|
|
1697
1823
|
def get_header_record(table, fptr)
|
1698
1824
|
fptr.seek(0)
|
1699
1825
|
|
1700
|
-
|
1701
|
-
|
1826
|
+
line = fptr.readline.chomp
|
1827
|
+
|
1828
|
+
if line[0..0] == 'Z'
|
1829
|
+
return [true, unencrypt_str(line[1..-1])]
|
1702
1830
|
else
|
1703
|
-
return
|
1831
|
+
return [false, line]
|
1704
1832
|
end
|
1705
1833
|
end
|
1706
1834
|
|
@@ -1708,8 +1836,8 @@ class KBEngine
|
|
1708
1836
|
# incr_rec_no_ctr
|
1709
1837
|
#-----------------------------------------------------------------------
|
1710
1838
|
def incr_rec_no_ctr(table, fptr)
|
1711
|
-
|
1712
|
-
|
1839
|
+
encrypted, header_line = get_header_record(table, fptr)
|
1840
|
+
last_rec_no, rest_of_line = header_line.split('|', 2)
|
1713
1841
|
last_rec_no = last_rec_no.to_i + 1
|
1714
1842
|
|
1715
1843
|
write_header_record(table, fptr, ['%06d' % last_rec_no,
|
@@ -1723,8 +1851,8 @@ class KBEngine
|
|
1723
1851
|
# incr_del_ctr
|
1724
1852
|
#-----------------------------------------------------------------------
|
1725
1853
|
def incr_del_ctr(table, fptr)
|
1726
|
-
|
1727
|
-
|
1854
|
+
encrypted, header_line = get_header_record(table, fptr)
|
1855
|
+
last_rec_no, del_ctr, rest_of_line = header_line.split('|', 3)
|
1728
1856
|
del_ctr = del_ctr.to_i + 1
|
1729
1857
|
|
1730
1858
|
write_header_record(table, fptr, [last_rec_no, '%06d' % del_ctr,
|
@@ -1732,60 +1860,6 @@ class KBEngine
|
|
1732
1860
|
|
1733
1861
|
return true
|
1734
1862
|
end
|
1735
|
-
|
1736
|
-
#-----------------------------------------------------------------------
|
1737
|
-
# encrypt_str
|
1738
|
-
#-----------------------------------------------------------------------
|
1739
|
-
def encrypt_str(s)
|
1740
|
-
# Returns an encrypted string, using the Vignere Cipher.
|
1741
|
-
|
1742
|
-
new_str = ''
|
1743
|
-
i_key = -1
|
1744
|
-
s.each_byte do |c|
|
1745
|
-
if i_key < EN_KEY_LEN - 1
|
1746
|
-
i_key += 1
|
1747
|
-
else
|
1748
|
-
i_key = 0
|
1749
|
-
end
|
1750
|
-
|
1751
|
-
if EN_STR.index(c.chr).nil?
|
1752
|
-
new_str << c.chr
|
1753
|
-
next
|
1754
|
-
end
|
1755
|
-
|
1756
|
-
i_from_str = EN_STR.index(EN_KEY[i_key]) + EN_STR.index(c.chr)
|
1757
|
-
i_from_str = i_from_str - EN_STR_LEN if i_from_str >= EN_STR_LEN
|
1758
|
-
new_str << EN_STR[i_from_str]
|
1759
|
-
end
|
1760
|
-
return new_str
|
1761
|
-
end
|
1762
|
-
|
1763
|
-
#-----------------------------------------------------------------------
|
1764
|
-
# unencrypt_str
|
1765
|
-
#-----------------------------------------------------------------------
|
1766
|
-
def unencrypt_str(s)
|
1767
|
-
# Returns an unencrypted string, using the Vignere Cipher.
|
1768
|
-
|
1769
|
-
new_str = ''
|
1770
|
-
i_key = -1
|
1771
|
-
s.each_byte do |c|
|
1772
|
-
if i_key < EN_KEY_LEN - 1
|
1773
|
-
i_key += 1
|
1774
|
-
else
|
1775
|
-
i_key = 0
|
1776
|
-
end
|
1777
|
-
|
1778
|
-
if EN_STR.index(c.chr).nil?
|
1779
|
-
new_str << c.chr
|
1780
|
-
next
|
1781
|
-
end
|
1782
|
-
|
1783
|
-
i_from_str = EN_STR.index(c.chr) - EN_STR.index(EN_KEY[i_key])
|
1784
|
-
i_from_str = i_from_str + EN_STR_LEN if i_from_str < 0
|
1785
|
-
new_str << EN_STR[i_from_str]
|
1786
|
-
end
|
1787
|
-
return new_str
|
1788
|
-
end
|
1789
1863
|
end
|
1790
1864
|
|
1791
1865
|
|
@@ -1794,6 +1868,7 @@ end
|
|
1794
1868
|
#---------------------------------------------------------------------------
|
1795
1869
|
class KBTable
|
1796
1870
|
include DRb::DRbUndumped
|
1871
|
+
include KBTypeConversionsMixin
|
1797
1872
|
|
1798
1873
|
# Make constructor private. KBTable instances should only be created
|
1799
1874
|
# from KirbyBase#get_table.
|
@@ -1806,14 +1881,11 @@ class KBTable
|
|
1806
1881
|
VALID_INDEX_TYPES = [:String, :Integer, :Float, :Boolean, :Date, :Time,
|
1807
1882
|
:DateTime]
|
1808
1883
|
|
1809
|
-
|
1810
|
-
|
1811
|
-
ENCODE_RE = /&|\n|\r|\032|\|/
|
1812
|
-
|
1813
|
-
attr_reader :filename, :name, :table_class, :db, :lookup_key
|
1884
|
+
attr_reader :filename, :name, :table_class, :db, :lookup_key, \
|
1885
|
+
:last_rec_no, :del_ctr
|
1814
1886
|
|
1815
1887
|
#-----------------------------------------------------------------------
|
1816
|
-
# KBTable.valid_field_type
|
1888
|
+
# KBTable.valid_field_type?
|
1817
1889
|
#-----------------------------------------------------------------------
|
1818
1890
|
#++
|
1819
1891
|
# Return true if valid field type.
|
@@ -1825,64 +1897,64 @@ class KBTable
|
|
1825
1897
|
end
|
1826
1898
|
|
1827
1899
|
#-----------------------------------------------------------------------
|
1828
|
-
# KBTable.
|
1900
|
+
# KBTable.valid_data_type?
|
1829
1901
|
#-----------------------------------------------------------------------
|
1830
1902
|
#++
|
1831
|
-
# Return true if
|
1903
|
+
# Return true if data is correct type, false otherwise.
|
1832
1904
|
#
|
1833
|
-
# *
|
1905
|
+
# *data_type*:: Symbol specifying data type.
|
1906
|
+
# *value*:: Value to convert to String.
|
1834
1907
|
#
|
1835
|
-
def KBTable.
|
1836
|
-
|
1908
|
+
def KBTable.valid_data_type?(data_type, value)
|
1909
|
+
case data_type
|
1910
|
+
when /:String|:Blob/
|
1911
|
+
return false unless value.respond_to?(:to_str)
|
1912
|
+
when :Memo
|
1913
|
+
return false unless value.is_a?(KBMemo)
|
1914
|
+
when :Blob
|
1915
|
+
return false unless value.is_a?(KBBlob)
|
1916
|
+
when :Boolean
|
1917
|
+
return false unless value.is_a?(TrueClass) or value.is_a?(
|
1918
|
+
FalseClass)
|
1919
|
+
when :Integer
|
1920
|
+
return false unless value.respond_to?(:to_int)
|
1921
|
+
when :Float
|
1922
|
+
return false unless value.respond_to?(:to_f)
|
1923
|
+
when :Time
|
1924
|
+
return false unless value.is_a?(Time)
|
1925
|
+
when :Date
|
1926
|
+
return false unless value.is_a?(Date)
|
1927
|
+
when :DateTime
|
1928
|
+
return false unless value.is_a?(DateTime)
|
1929
|
+
when :YAML
|
1930
|
+
return false unless value.respond_to?(:to_yaml)
|
1931
|
+
end
|
1932
|
+
|
1933
|
+
return true
|
1837
1934
|
end
|
1838
1935
|
|
1839
1936
|
#-----------------------------------------------------------------------
|
1840
|
-
# KBTable.
|
1937
|
+
# KBTable.valid_default_type?
|
1841
1938
|
#-----------------------------------------------------------------------
|
1842
1939
|
#++
|
1843
|
-
# Return true if valid
|
1940
|
+
# Return true if valid default type.
|
1844
1941
|
#
|
1845
1942
|
# *field_type*:: Symbol specifying field type.
|
1846
1943
|
#
|
1847
|
-
def KBTable.
|
1848
|
-
|
1944
|
+
def KBTable.valid_default_type?(field_type)
|
1945
|
+
VALID_DEFAULT_TYPES.include?(field_type)
|
1849
1946
|
end
|
1850
1947
|
|
1851
1948
|
#-----------------------------------------------------------------------
|
1852
|
-
# KBTable.
|
1949
|
+
# KBTable.valid_index_type?
|
1853
1950
|
#-----------------------------------------------------------------------
|
1854
1951
|
#++
|
1855
|
-
# Return
|
1952
|
+
# Return true if valid index type.
|
1856
1953
|
#
|
1857
|
-
# *
|
1858
|
-
# *value*:: Value to convert to String.
|
1954
|
+
# *field_type*:: Symbol specifying field type.
|
1859
1955
|
#
|
1860
|
-
def KBTable.
|
1861
|
-
|
1862
|
-
when :YAML
|
1863
|
-
y = value.to_yaml
|
1864
|
-
if y =~ ENCODE_RE
|
1865
|
-
return y.gsub("&", '&').gsub("\n", '&linefeed;').gsub(
|
1866
|
-
"\r", '&carriage_return;').gsub("\032", '&substitute;'
|
1867
|
-
).gsub("|", '&pipe;')
|
1868
|
-
else
|
1869
|
-
return y
|
1870
|
-
end
|
1871
|
-
when :String
|
1872
|
-
if value =~ ENCODE_RE
|
1873
|
-
return value.gsub("&", '&').gsub("\n", '&linefeed;'
|
1874
|
-
).gsub("\r", '&carriage_return;').gsub("\032",
|
1875
|
-
'&substitute;').gsub("|", '&pipe;')
|
1876
|
-
else
|
1877
|
-
return value
|
1878
|
-
end
|
1879
|
-
when :Memo
|
1880
|
-
return value.filepath
|
1881
|
-
when :Blob
|
1882
|
-
return value.filepath
|
1883
|
-
else
|
1884
|
-
return value.to_s
|
1885
|
-
end
|
1956
|
+
def KBTable.valid_index_type?(field_type)
|
1957
|
+
VALID_INDEX_TYPES.include?(field_type)
|
1886
1958
|
end
|
1887
1959
|
|
1888
1960
|
#-----------------------------------------------------------------------
|
@@ -2017,8 +2089,8 @@ class KBTable
|
|
2017
2089
|
# Update the header variables.
|
2018
2090
|
update_header_vars
|
2019
2091
|
|
2020
|
-
# Convert input, which could be an array, a hash, or a
|
2021
|
-
# into a common format (i.e. hash).
|
2092
|
+
# Convert input, which could be a proc, an array, a hash, or a
|
2093
|
+
# Struct into a common format (i.e. hash).
|
2022
2094
|
if data.empty?
|
2023
2095
|
input_rec = convert_input_data(insert_proc)
|
2024
2096
|
else
|
@@ -2028,36 +2100,35 @@ class KBTable
|
|
2028
2100
|
# Check the field values to make sure they are proper types.
|
2029
2101
|
validate_input(input_rec)
|
2030
2102
|
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2103
|
+
input_rec = Struct.new(*field_names).new(*field_names.zip(
|
2104
|
+
@field_defaults).collect do |fn, fd|
|
2105
|
+
if input_rec[fn].nil?
|
2106
|
+
fd
|
2107
|
+
else
|
2108
|
+
input_rec[fn]
|
2109
|
+
end
|
2110
|
+
end)
|
2040
2111
|
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
end
|
2050
|
-
else
|
2051
|
-
KBTable.convert_to_string(ft, input_rec[fn])
|
2052
|
-
end
|
2112
|
+
check_required_fields(input_rec)
|
2113
|
+
|
2114
|
+
check_against_input_for_specials(input_rec)
|
2115
|
+
|
2116
|
+
new_recno = @db.engine.insert_record(self, @field_names.zip(
|
2117
|
+
@field_types).collect do |fn, ft|
|
2118
|
+
if input_rec[fn].nil?
|
2119
|
+
''
|
2053
2120
|
else
|
2054
|
-
|
2055
|
-
''
|
2056
|
-
else
|
2057
|
-
KBTable.convert_to_string(ft, fd)
|
2058
|
-
end
|
2121
|
+
convert_to_encoded_string(ft, input_rec[fn])
|
2059
2122
|
end
|
2060
2123
|
end)
|
2124
|
+
|
2125
|
+
# If there are any associated memo/blob fields, save their values.
|
2126
|
+
input_rec.each { |r| r.write_to_file if r.is_a?(KBMemo) } if \
|
2127
|
+
@field_types.include?(:Memo)
|
2128
|
+
input_rec.each { |r| r.write_to_file if r.is_a?(KBBlob) } if \
|
2129
|
+
@field_types.include?(:Blob)
|
2130
|
+
|
2131
|
+
return new_recno
|
2061
2132
|
end
|
2062
2133
|
|
2063
2134
|
#-----------------------------------------------------------------------
|
@@ -2069,8 +2140,23 @@ class KBTable
|
|
2069
2140
|
#
|
2070
2141
|
# *updates*:: Hash or Struct containing updates.
|
2071
2142
|
#
|
2072
|
-
def update_all(*updates)
|
2073
|
-
|
2143
|
+
def update_all(*updates, &update_proc)
|
2144
|
+
raise 'Cannot specify both a hash/array/struct and a ' + \
|
2145
|
+
'proc for method #update_all!' unless updates.empty? or \
|
2146
|
+
update_proc.nil?
|
2147
|
+
|
2148
|
+
raise 'Must specify either hash/array/struct or update ' + \
|
2149
|
+
'proc for method #update_all!' if updates.empty? and \
|
2150
|
+
update_proc.nil?
|
2151
|
+
|
2152
|
+
# Depending on whether the user supplied an array/hash/struct or a
|
2153
|
+
# block as update criteria, we are going to call updates in one of
|
2154
|
+
# two ways.
|
2155
|
+
if updates.empty?
|
2156
|
+
update { true }.set &update_proc
|
2157
|
+
else
|
2158
|
+
update(*updates) { true }
|
2159
|
+
end
|
2074
2160
|
end
|
2075
2161
|
|
2076
2162
|
#-----------------------------------------------------------------------
|
@@ -2094,8 +2180,13 @@ class KBTable
|
|
2094
2180
|
# return them in an array.
|
2095
2181
|
result_set = get_matches(:update, @field_names, select_cond)
|
2096
2182
|
|
2183
|
+
# If updates is empty, this means that the user must have specified
|
2184
|
+
# the updates in KBResultSet#set, i.e.
|
2185
|
+
# tbl.update {|r| r.recno == 1}.set(:name => 'Bob')
|
2097
2186
|
return result_set if updates.empty?
|
2098
2187
|
|
2188
|
+
# Call KBTable#set and pass it the records to be updated and the
|
2189
|
+
# updated criteria.
|
2099
2190
|
set(result_set, updates)
|
2100
2191
|
end
|
2101
2192
|
|
@@ -2123,37 +2214,64 @@ class KBTable
|
|
2123
2214
|
# *data*:: Hash, Struct, Proc containing updates.
|
2124
2215
|
#
|
2125
2216
|
def set(recs, data)
|
2126
|
-
#
|
2127
|
-
# into a common format (i.e.
|
2128
|
-
|
2217
|
+
# If updates are not in the form of a Proc, convert updates, which
|
2218
|
+
# could be an array, a hash, or a Struct into a common format (i.e.
|
2219
|
+
# hash).
|
2220
|
+
update_rec = convert_input_data(data) unless data.is_a?(Proc)
|
2129
2221
|
|
2130
|
-
# Make sure all of the fields of the update rec are of the proper
|
2131
|
-
# type.
|
2132
|
-
validate_input(update_rec)
|
2133
|
-
|
2134
|
-
if @field_types.include?(:Memo)
|
2135
|
-
update_rec.each_value { |r| r.write_to_file if r.is_a?(KBMemo) }
|
2136
|
-
end
|
2137
|
-
|
2138
|
-
if @field_types.include?(:Blob)
|
2139
|
-
update_rec.each_value { |r| r.write_to_file if r.is_a?(KBBlob) }
|
2140
|
-
end
|
2141
|
-
|
2142
2222
|
updated_recs = []
|
2143
2223
|
|
2144
2224
|
# For each one of the recs that matched the update query, apply the
|
2145
2225
|
# updates to it and write it back to the database table.
|
2146
2226
|
recs.each do |rec|
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2227
|
+
temp_rec = rec.dup
|
2228
|
+
|
2229
|
+
if data.is_a?(Proc)
|
2230
|
+
begin
|
2231
|
+
data.call(temp_rec)
|
2232
|
+
rescue NoMethodError
|
2233
|
+
raise 'Invalid field name in code block: %s' % $!
|
2234
|
+
end
|
2235
|
+
else
|
2236
|
+
@field_names.each { |fn| temp_rec[fn] = update_rec.fetch(fn,
|
2237
|
+
temp_rec.send(fn)) }
|
2238
|
+
end
|
2239
|
+
|
2240
|
+
# Is the user trying to change something they shouldn't?
|
2241
|
+
raise 'Cannot update recno field!' unless \
|
2242
|
+
rec.recno == temp_rec.recno
|
2243
|
+
raise 'Cannot update internal fpos field!' unless \
|
2244
|
+
rec.fpos == temp_rec.fpos
|
2245
|
+
raise 'Cannot update internal line_length field!' unless \
|
2246
|
+
rec.line_length == temp_rec.line_length
|
2247
|
+
|
2248
|
+
# Are the data types of the updates correct?
|
2249
|
+
validate_input(temp_rec)
|
2250
|
+
|
2251
|
+
check_required_fields(temp_rec)
|
2252
|
+
|
2253
|
+
check_against_input_for_specials(temp_rec)
|
2254
|
+
|
2255
|
+
# Apply updates to the record and add it to an array holding
|
2256
|
+
# updated records. We need the fpos and line_length because
|
2257
|
+
# the engine will use them to determine where to write the
|
2258
|
+
# update and whether the updated record will fit in the old
|
2259
|
+
# record's spot.
|
2260
|
+
updated_recs << { :rec => @field_names.zip(@field_types
|
2261
|
+
).collect { |fn, ft| convert_to_encoded_string(ft,
|
2262
|
+
temp_rec.send(fn)) }, :fpos => rec.fpos,
|
2263
|
+
:line_length => rec.line_length }
|
2264
|
+
|
2265
|
+
|
2266
|
+
# Update any associated blob/memo fields.
|
2267
|
+
temp_rec.each { |r| r.write_to_file if r.is_a?(KBMemo) } if \
|
2268
|
+
@field_types.include?(:Memo)
|
2269
|
+
temp_rec.each { |r| r.write_to_file if r.is_a?(KBBlob) } if \
|
2270
|
+
@field_types.include?(:Blob)
|
2271
|
+
end
|
2272
|
+
|
2273
|
+
# Take all of the update records and write them back out to the
|
2274
|
+
# table's file.
|
2157
2275
|
@db.engine.update_records(self, updated_recs)
|
2158
2276
|
|
2159
2277
|
# Return the number of records updated.
|
@@ -2193,10 +2311,12 @@ class KBTable
|
|
2193
2311
|
# be reset to 0.
|
2194
2312
|
#
|
2195
2313
|
def clear(reset_recno_ctr=true)
|
2196
|
-
delete { true }
|
2314
|
+
recs_deleted = delete { true }
|
2197
2315
|
pack
|
2198
2316
|
|
2199
2317
|
@db.engine.reset_recno_ctr(self) if reset_recno_ctr
|
2318
|
+
update_header_vars
|
2319
|
+
return recs_deleted
|
2200
2320
|
end
|
2201
2321
|
|
2202
2322
|
#-----------------------------------------------------------------------
|
@@ -2278,6 +2398,9 @@ class KBTable
|
|
2278
2398
|
# Remove blank records from table, return total removed.
|
2279
2399
|
#
|
2280
2400
|
def pack
|
2401
|
+
raise "Do not execute this method in client/server mode!" if \
|
2402
|
+
@db.client?
|
2403
|
+
|
2281
2404
|
lines_deleted = @db.engine.pack_table(self)
|
2282
2405
|
|
2283
2406
|
update_header_vars
|
@@ -2386,6 +2509,9 @@ class KBTable
|
|
2386
2509
|
|
2387
2510
|
raise "Column name cannot be recno!" if col_name == :recno
|
2388
2511
|
|
2512
|
+
raise "Column name already exists!" if @field_names.include?(
|
2513
|
+
col_name)
|
2514
|
+
|
2389
2515
|
# Does this new column have field extras (i.e. Index, Lookup, etc.)
|
2390
2516
|
if col_type.is_a?(Hash)
|
2391
2517
|
temp_type = col_type[:DataType]
|
@@ -2396,7 +2522,9 @@ class KBTable
|
|
2396
2522
|
raise 'Invalid field type: %s' % temp_type unless \
|
2397
2523
|
KBTable.valid_field_type?(temp_type)
|
2398
2524
|
|
2399
|
-
@db.
|
2525
|
+
field_def = @db.build_header_field_string(col_name, col_type)
|
2526
|
+
|
2527
|
+
@db.engine.add_column(self, field_def, after)
|
2400
2528
|
|
2401
2529
|
# Need to reinitialize the table instance and associated indexes.
|
2402
2530
|
@db.engine.remove_recno_index(@name)
|
@@ -2547,7 +2675,7 @@ class KBTable
|
|
2547
2675
|
@db.engine.change_column_default_value(self, col_name, nil)
|
2548
2676
|
else
|
2549
2677
|
@db.engine.change_column_default_value(self, col_name,
|
2550
|
-
|
2678
|
+
convert_to_encoded_string(
|
2551
2679
|
@field_types[@field_names.index(col_name)], value))
|
2552
2680
|
end
|
2553
2681
|
|
@@ -2716,13 +2844,16 @@ class KBTable
|
|
2716
2844
|
END_OF_STRING
|
2717
2845
|
set_meth_str = <<-END_OF_STRING
|
2718
2846
|
def #{field_name}=(s)
|
2719
|
-
@#{field_name} =
|
2847
|
+
@#{field_name} = convert_to_native_type(:#{field_type}, s)
|
2720
2848
|
end
|
2721
2849
|
END_OF_STRING
|
2722
2850
|
|
2723
2851
|
# If this is a Lookup field, modify the get_method.
|
2724
2852
|
if field_extra.has_key?('Lookup')
|
2725
2853
|
lookup_table, key_field = field_extra['Lookup'].split('.')
|
2854
|
+
|
2855
|
+
# If joining to recno field of lookup table use the
|
2856
|
+
# KBTable[] method to get the record from the lookup table.
|
2726
2857
|
if key_field == 'recno'
|
2727
2858
|
get_meth_str = <<-END_OF_STRING
|
2728
2859
|
def #{field_name}
|
@@ -2741,7 +2872,7 @@ class KBTable
|
|
2741
2872
|
def #{field_name}
|
2742
2873
|
table = @tbl.db.get_table(:#{lookup_table})
|
2743
2874
|
return table.select_by_#{key_field}_index { |r|
|
2744
|
-
r.#{key_field} == @#{field_name} }
|
2875
|
+
r.#{key_field} == @#{field_name} }[0]
|
2745
2876
|
end
|
2746
2877
|
END_OF_STRING
|
2747
2878
|
rescue RuntimeError
|
@@ -2749,7 +2880,7 @@ class KBTable
|
|
2749
2880
|
def #{field_name}
|
2750
2881
|
table = @tbl.db.get_table(:#{lookup_table})
|
2751
2882
|
return table.select { |r|
|
2752
|
-
r.#{key_field} == @#{field_name} }
|
2883
|
+
r.#{key_field} == @#{field_name} }[0]
|
2753
2884
|
end
|
2754
2885
|
END_OF_STRING
|
2755
2886
|
end
|
@@ -2845,54 +2976,80 @@ class KBTable
|
|
2845
2976
|
# Convert data passed to #input, #update, or #set to a common format.
|
2846
2977
|
#
|
2847
2978
|
def convert_input_data(values)
|
2848
|
-
|
2849
|
-
|
2850
|
-
|
2979
|
+
temp_hash = {}
|
2980
|
+
|
2981
|
+
# This only applies to Procs in #insert, Procs in #update are
|
2982
|
+
# handled in #set.
|
2983
|
+
if values.is_a?(Proc)
|
2984
|
+
tbl_rec = Struct.new(*@field_names[1..-1]).new
|
2851
2985
|
begin
|
2852
2986
|
values.call(tbl_rec)
|
2853
2987
|
rescue NoMethodError
|
2854
2988
|
raise 'Invalid field name in code block: %s' % $!
|
2855
2989
|
end
|
2856
|
-
|
2857
|
-
@field_names[1..-1].
|
2990
|
+
|
2991
|
+
@field_names[1..-1].each do |f|
|
2858
2992
|
temp_hash[f] = tbl_rec[f] unless tbl_rec[f].nil?
|
2859
|
-
|
2860
|
-
|
2861
|
-
|
2862
|
-
|
2863
|
-
|
2864
|
-
|
2865
|
-
|
2866
|
-
|
2867
|
-
|
2868
|
-
|
2869
|
-
|
2870
|
-
|
2871
|
-
|
2872
|
-
|
2873
|
-
|
2874
|
-
|
2875
|
-
|
2876
|
-
return temp_hash
|
2877
|
-
elsif values[0].class == Array
|
2878
|
-
raise ArgumentError, 'Must specify all fields in input array!' \
|
2879
|
-
unless values[0].size == @field_names[1..-1].size
|
2880
|
-
temp_hash = {}
|
2881
|
-
@field_names[1..-1].collect { |f|
|
2882
|
-
temp_hash[f] = values[0][@field_names.index(f)-1]
|
2883
|
-
}
|
2884
|
-
return temp_hash
|
2885
|
-
elsif values.class == Array
|
2993
|
+
end
|
2994
|
+
|
2995
|
+
# Is input data an instance of custom record class, Struct, or
|
2996
|
+
# KBTableRec?
|
2997
|
+
elsif values.first.is_a?(Object.full_const_get(@record_class)) or \
|
2998
|
+
values.first.is_a?(Struct) or values.first.class == @table_class
|
2999
|
+
@field_names[1..-1].each do |f|
|
3000
|
+
temp_hash[f] = values.first.send(f) if \
|
3001
|
+
values.first.respond_to?(f)
|
3002
|
+
end
|
3003
|
+
|
3004
|
+
# Is input data a hash?
|
3005
|
+
elsif values.first.is_a?(Hash)
|
3006
|
+
temp_hash = values.first.dup
|
3007
|
+
|
3008
|
+
# Is input data an array?
|
3009
|
+
elsif values.is_a?(Array)
|
2886
3010
|
raise ArgumentError, 'Must specify all fields in input array!' \
|
2887
3011
|
unless values.size == @field_names[1..-1].size
|
2888
|
-
|
2889
|
-
@field_names[1..-1].
|
3012
|
+
|
3013
|
+
@field_names[1..-1].each do |f|
|
2890
3014
|
temp_hash[f] = values[@field_names.index(f)-1]
|
2891
|
-
|
2892
|
-
return temp_hash
|
3015
|
+
end
|
2893
3016
|
else
|
2894
3017
|
raise(ArgumentError, 'Invalid type for values container!')
|
2895
3018
|
end
|
3019
|
+
|
3020
|
+
return temp_hash
|
3021
|
+
end
|
3022
|
+
|
3023
|
+
#-----------------------------------------------------------------------
|
3024
|
+
# check_required_fields
|
3025
|
+
#-----------------------------------------------------------------------
|
3026
|
+
#++
|
3027
|
+
# Check that all required fields have values.
|
3028
|
+
#
|
3029
|
+
def check_required_fields(data)
|
3030
|
+
@field_names[1..-1].each do |f|
|
3031
|
+
raise(ArgumentError,
|
3032
|
+
'A value for this field is required: %s' % f) if \
|
3033
|
+
@field_requireds[@field_names.index(f)] and data[f].nil?
|
3034
|
+
end
|
3035
|
+
end
|
3036
|
+
|
3037
|
+
#-----------------------------------------------------------------------
|
3038
|
+
# check_against_input_for_specials
|
3039
|
+
#-----------------------------------------------------------------------
|
3040
|
+
#++
|
3041
|
+
# Check that no special field types (i.e. calculated or link_many
|
3042
|
+
# fields)
|
3043
|
+
# have been given values.
|
3044
|
+
#
|
3045
|
+
def check_against_input_for_specials(data)
|
3046
|
+
@field_names[1..-1].each do |f|
|
3047
|
+
raise(ArgumentError,
|
3048
|
+
'You cannot input a value for this field: %s' % f) if \
|
3049
|
+
@field_extras[@field_names.index(f)].has_key?('Calculated') \
|
3050
|
+
or @field_extras[@field_names.index(f)].has_key?('Link_many') \
|
3051
|
+
and not data[f].nil?
|
3052
|
+
end
|
2896
3053
|
end
|
2897
3054
|
|
2898
3055
|
#-----------------------------------------------------------------------
|
@@ -2902,49 +3059,12 @@ class KBTable
|
|
2902
3059
|
# Check input data to ensure proper data types.
|
2903
3060
|
#
|
2904
3061
|
def validate_input(data)
|
2905
|
-
raise 'Cannot insert/update recno field!' if data.has_key?(:recno)
|
2906
|
-
|
2907
3062
|
@field_names[1..-1].each do |f|
|
2908
|
-
next
|
3063
|
+
next if data[f].nil?
|
2909
3064
|
|
2910
|
-
|
2911
|
-
|
2912
|
-
|
2913
|
-
next
|
2914
|
-
end
|
2915
|
-
|
2916
|
-
case @field_types[@field_names.index(f)]
|
2917
|
-
when /:String|:Blob/
|
2918
|
-
raise 'Invalid String value for: %s' % f unless \
|
2919
|
-
data[f].respond_to?(:to_str)
|
2920
|
-
when :Memo
|
2921
|
-
raise 'Invalid Memo value for: %s' % f unless \
|
2922
|
-
data[f].is_a?(KBMemo)
|
2923
|
-
when :Blob
|
2924
|
-
raise 'Invalid Blob value for: %s' % f unless \
|
2925
|
-
data[f].is_a?(KBBlob)
|
2926
|
-
when :Boolean
|
2927
|
-
raise 'Invalid Boolean value for: %s' % f unless \
|
2928
|
-
data[f].is_a?(TrueClass) or data[f].kind_of?(FalseClass)
|
2929
|
-
when :Integer
|
2930
|
-
raise 'Invalid Integer value for: %s' % f unless \
|
2931
|
-
data[f].respond_to?(:to_int)
|
2932
|
-
when :Float
|
2933
|
-
raise 'Invalid Float value for: %s' % f unless \
|
2934
|
-
data[f].respond_to?(:to_f)
|
2935
|
-
when :Time
|
2936
|
-
raise 'Invalid Time value for: %s' % f unless \
|
2937
|
-
data[f].is_a?(Time)
|
2938
|
-
when :Date
|
2939
|
-
raise 'Invalid Date value for: %s' % f unless \
|
2940
|
-
data[f].is_a?(Date)
|
2941
|
-
when :DateTime
|
2942
|
-
raise 'Invalid DateTime value for: %s' % f unless \
|
2943
|
-
data[f].is_a?(DateTime)
|
2944
|
-
when :YAML
|
2945
|
-
raise 'Invalid YAML value for: %s' % f unless \
|
2946
|
-
data[f].respond_to?(:to_yaml)
|
2947
|
-
end
|
3065
|
+
raise 'Invalid data %s for column %s' % [data[f], f] unless \
|
3066
|
+
KBTable.valid_data_type?(@field_types[@field_names.index(f)],
|
3067
|
+
data[f])
|
2948
3068
|
end
|
2949
3069
|
end
|
2950
3070
|
|
@@ -2963,6 +3083,9 @@ class KBTable
|
|
2963
3083
|
#-----------------------------------------------------------------------
|
2964
3084
|
# get_result_struct
|
2965
3085
|
#-----------------------------------------------------------------------
|
3086
|
+
#++
|
3087
|
+
# Return Struct object that will hold result record.
|
3088
|
+
#
|
2966
3089
|
def get_result_struct(query_type, filter)
|
2967
3090
|
case query_type
|
2968
3091
|
when :select
|
@@ -2978,6 +3101,9 @@ class KBTable
|
|
2978
3101
|
#-----------------------------------------------------------------------
|
2979
3102
|
# create_result_rec
|
2980
3103
|
#-----------------------------------------------------------------------
|
3104
|
+
#++
|
3105
|
+
# Return Struct/custom class populated with table row data.
|
3106
|
+
#
|
2981
3107
|
def create_result_rec(query_type, filter, result_struct, tbl_rec, rec)
|
2982
3108
|
# If this isn't a select query or if it is a select query, but
|
2983
3109
|
# the table record class is simply a Struct, then we will use
|
@@ -3036,11 +3162,11 @@ class KBTable
|
|
3036
3162
|
# Loop through table.
|
3037
3163
|
@db.engine.get_recs(self).each do |rec|
|
3038
3164
|
tbl_rec.populate(rec)
|
3039
|
-
|
3165
|
+
|
3166
|
+
next if select_cond and not select_cond.call(tbl_rec)
|
3040
3167
|
|
3041
3168
|
match_array << create_result_rec(query_type, filter,
|
3042
3169
|
result_struct, tbl_rec, rec)
|
3043
|
-
|
3044
3170
|
end
|
3045
3171
|
return match_array
|
3046
3172
|
end
|
@@ -3099,16 +3225,14 @@ class KBTable
|
|
3099
3225
|
#
|
3100
3226
|
def get_matches_by_recno_index(query_type, filter, select_cond)
|
3101
3227
|
good_matches = []
|
3102
|
-
|
3103
3228
|
idx_struct = Struct.new(:recno)
|
3104
3229
|
|
3105
3230
|
begin
|
3106
3231
|
@db.engine.get_recno_index(self).each_key do |key|
|
3107
|
-
good_matches << key if select_cond.call(
|
3108
|
-
idx_struct.new(key))
|
3232
|
+
good_matches << key if select_cond.call(idx_struct.new(key))
|
3109
3233
|
end
|
3110
3234
|
rescue NoMethodError
|
3111
|
-
raise "
|
3235
|
+
raise "You can only use recno field in select block!"
|
3112
3236
|
end
|
3113
3237
|
|
3114
3238
|
return nil if good_matches.empty?
|
@@ -3150,7 +3274,6 @@ class KBTable
|
|
3150
3274
|
tbl_rec = @table_class.new(self)
|
3151
3275
|
|
3152
3276
|
@db.engine.get_recs_by_recno(self, recnos).each do |rec|
|
3153
|
-
|
3154
3277
|
next if rec.nil?
|
3155
3278
|
tbl_rec.populate(rec)
|
3156
3279
|
|
@@ -3177,15 +3300,22 @@ class KBMemo
|
|
3177
3300
|
@contents = contents
|
3178
3301
|
end
|
3179
3302
|
|
3303
|
+
#-----------------------------------------------------------------------
|
3304
|
+
# read_from_file
|
3305
|
+
#-----------------------------------------------------------------------
|
3180
3306
|
def read_from_file
|
3181
3307
|
@contents = @db.engine.read_memo_file(@filepath)
|
3182
3308
|
end
|
3183
3309
|
|
3310
|
+
#-----------------------------------------------------------------------
|
3311
|
+
# write_to_file
|
3312
|
+
#-----------------------------------------------------------------------
|
3184
3313
|
def write_to_file
|
3185
3314
|
@db.engine.write_memo_file(@filepath, @contents)
|
3186
3315
|
end
|
3187
3316
|
end
|
3188
3317
|
|
3318
|
+
|
3189
3319
|
#---------------------------------------------------------------------------
|
3190
3320
|
# KBBlob
|
3191
3321
|
#---------------------------------------------------------------------------
|
@@ -3201,10 +3331,16 @@ class KBBlob
|
|
3201
3331
|
@contents = contents
|
3202
3332
|
end
|
3203
3333
|
|
3334
|
+
#-----------------------------------------------------------------------
|
3335
|
+
# read_from_file
|
3336
|
+
#-----------------------------------------------------------------------
|
3204
3337
|
def read_from_file
|
3205
3338
|
@contents = @db.engine.read_blob_file(@filepath)
|
3206
3339
|
end
|
3207
3340
|
|
3341
|
+
#-----------------------------------------------------------------------
|
3342
|
+
# write_to_file
|
3343
|
+
#-----------------------------------------------------------------------
|
3208
3344
|
def write_to_file
|
3209
3345
|
@db.engine.write_blob_file(@filepath, @contents)
|
3210
3346
|
end
|
@@ -3216,8 +3352,7 @@ end
|
|
3216
3352
|
#---------------------------------------------------------------------------
|
3217
3353
|
class KBIndex
|
3218
3354
|
include KBTypeConversionsMixin
|
3219
|
-
|
3220
|
-
UNENCODE_RE = /&(?:amp|linefeed|carriage_return|substitute|pipe);/
|
3355
|
+
include KBEncryptionMixin
|
3221
3356
|
|
3222
3357
|
#-----------------------------------------------------------------------
|
3223
3358
|
# initialize
|
@@ -3277,7 +3412,8 @@ class KBIndex
|
|
3277
3412
|
# native types.
|
3278
3413
|
idx_rec = []
|
3279
3414
|
@col_poss.zip(@col_types).each do |col_pos, col_type|
|
3280
|
-
idx_rec <<
|
3415
|
+
idx_rec << convert_to_native_type(col_type,
|
3416
|
+
rec[col_pos])
|
3281
3417
|
end
|
3282
3418
|
|
3283
3419
|
# Were all the index fields for this record equal to NULL?
|
@@ -3303,7 +3439,7 @@ class KBIndex
|
|
3303
3439
|
#-----------------------------------------------------------------------
|
3304
3440
|
def add_index_rec(rec)
|
3305
3441
|
@idx_arr << @col_poss.zip(@col_types).collect do |col_pos, col_type|
|
3306
|
-
|
3442
|
+
convert_to_native_type(col_type, rec[col_pos])
|
3307
3443
|
end + [rec.first.to_i]
|
3308
3444
|
|
3309
3445
|
@last_update = Time.new
|
@@ -3332,7 +3468,7 @@ end
|
|
3332
3468
|
# KBRecnoIndex
|
3333
3469
|
#---------------------------------------------------------------------------
|
3334
3470
|
class KBRecnoIndex
|
3335
|
-
|
3471
|
+
include KBEncryptionMixin
|
3336
3472
|
|
3337
3473
|
#-----------------------------------------------------------------------
|
3338
3474
|
# initialize
|
@@ -3345,7 +3481,7 @@ class KBRecnoIndex
|
|
3345
3481
|
#-----------------------------------------------------------------------
|
3346
3482
|
# get_idx
|
3347
3483
|
#-----------------------------------------------------------------------
|
3348
|
-
def get_idx
|
3484
|
+
def get_idx
|
3349
3485
|
return @idx_hash
|
3350
3486
|
end
|
3351
3487
|
|