KirbyBase 2.5 → 2.5.1
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/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
|
|