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 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
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'kirbybase'
4
4
  require 'drb'
5
- require 'benchmark'
6
- include Benchmark
7
5
 
8
6
  host = ''
9
7
  port = 44444
@@ -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 = 1400
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 code block, via the set command.
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
- # 4) Update record by treating table as if it were a Hash and the keys were
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 }
@@ -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>&lt;<a href="mailto:jcribbs@twmi.rr.com">jcribbs@twmi.rr.com</a>&gt;</tt></span><br />
265
- <span id="revision">version 2.5,</span>
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="#client-server-diagram">Client/Server memory space diagram</a>
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="#single-user-diagram">Single-user memory space diagram</a>
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 dbms is in one (approx. 100k) code
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 it's lifetime.
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.ext = '.dat' }</tt></pre>
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. This will be applied whenever either no value or a nil value is
873
- specified for the field when <strong>inserting</strong> a record only. When updating an
874
- existing record, defaults will not be applied.</p>
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 it's value set to whatever we want the default to
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 it's value set to true.</p>
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 it's value set from 1 to 5. For compound indexes, like
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>Please let me know if this poses a problem for any of your applications. I
965
- have some ideas that I could implement that would allow the developer to
966
- specify when indexes are created.</p>
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 it's fields would
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 it's value is always computed at runtime.</p>
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 it's value is always computed at runtime.</p>
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</tt></pre>
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 &gt; 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. It's as simple as that.</p>
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=&gt;: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 it's associated detail items:</p>
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
- it's members using field names. So, to print the name and speed of each
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 it's there if you prefer to use it.</p>
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
- it's range to 1210 miles, you could code:</p>
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=&gt;405, :range=&gt;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 it's recno to a
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, it's name will show up here and records
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
- dbms. This dbms server handles table locking for you so you don't have to
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 it's fields. Even though you take a hit when KirbyBase first
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="client-server-diagram"></a>Client/Server memory space diagram</h2>
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/client_server.png" alt="images/client_server.png"/>
2237
+ <img src="images/single_user.png" alt="images/single_user.png"/>
2207
2238
  </div>
2208
- <div class="image-title">Figure: Client/Server (separate memory spaces) mode.</div>
2239
+ <div class="image-title">Figure: Single-user (embedded) mode.</div>
2209
2240
  </div>
2210
2241
  </div>
2211
- <h2><a id="single-user-diagram"></a>Single-user memory space diagram</h2>
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/single_user.png" alt="images/single_user.png"/>
2246
+ <img src="images/client_server.png" alt="images/client_server.png"/>
2216
2247
  </div>
2217
- <div class="image-title">Figure: Single-user (embedded) mode.</div>
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
- Version 2.5<br />
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>
@@ -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('&amp;', "&")
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('&amp;', "&")
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("&", '&amp;').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("&", '&amp;').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 use to only execute the code below if this was either a
214
- # single-user instance of KirbyBase or if client-server, I would
215
- # only let the client-side KirbyBase instance create the table
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
- @engine.new_table(t.name, t.field_defs, t.encrypt,
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('&amp;', "&")
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('&amp;', "&")
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
- temp_field_defs].join('|')
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
- last_rec_no, rest_of_line = get_header_record(table, fptr
811
- ).split('|', 2)
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
- convert_to(field_types[i], y.split('->')[1])
977
+ convert_to_native_type(field_types[i],
978
+ y.split('->')[1])
842
979
  elsif y =~ /Required/
843
980
  field_requireds[i] = \
844
- convert_to(:Boolean, y.split('->')[1])
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 [table.encrypted?, last_rec_no.to_i, del_ctr.to_i,
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. Then read first
873
- # detail record.
1010
+ # Record current position in table.
874
1011
  fpos = fptr.tell
875
- line = fptr.readline
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
- # If blank line (i.e. 'deleted'), skip it.
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
- line = fptr.readline
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
- # If blank line (i.e. 'deleted'), skip it.
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
- line = fptr.readline
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
- return nil if line == ''
1069
+ raise "Recno Index Corrupt for table %s!" % table.name if \
1070
+ rec.empty?
954
1071
 
955
- # Split the line up into fields.
956
- rec = line.split('|', -1)
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, col_name, col_type, after)
1134
- temp_field_def = build_header_field_string(col_name, col_type)
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
- if table.field_names.last == after
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
- if insert_after == -1
1155
- header_rec.insert(insert_after, temp_field_def)
1156
- else
1157
- header_rec.insert(insert_after+3, temp_field_def)
1158
- end
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
- if table.encrypted?
1701
- return unencrypt_str(fptr.readline[1..-1].chomp)
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 fptr.readline.chomp
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
- last_rec_no, rest_of_line = get_header_record(table, fptr).split(
1712
- '|', 2)
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
- last_rec_no, del_ctr, rest_of_line = get_header_record(table,
1727
- fptr).split('|', 3)
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
- # Regular expression used to determine if field needs to be
1810
- # encoded.
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.valid_default_type
1900
+ # KBTable.valid_data_type?
1829
1901
  #-----------------------------------------------------------------------
1830
1902
  #++
1831
- # Return true if valid default type.
1903
+ # Return true if data is correct type, false otherwise.
1832
1904
  #
1833
- # *field_type*:: Symbol specifying field type.
1905
+ # *data_type*:: Symbol specifying data type.
1906
+ # *value*:: Value to convert to String.
1834
1907
  #
1835
- def KBTable.valid_default_type?(field_type)
1836
- VALID_DEFAULT_TYPES.include?(field_type)
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.valid_index_type
1937
+ # KBTable.valid_default_type?
1841
1938
  #-----------------------------------------------------------------------
1842
1939
  #++
1843
- # Return true if valid index type.
1940
+ # Return true if valid default type.
1844
1941
  #
1845
1942
  # *field_type*:: Symbol specifying field type.
1846
1943
  #
1847
- def KBTable.valid_index_type?(field_type)
1848
- VALID_INDEX_TYPES.include?(field_type)
1944
+ def KBTable.valid_default_type?(field_type)
1945
+ VALID_DEFAULT_TYPES.include?(field_type)
1849
1946
  end
1850
1947
 
1851
1948
  #-----------------------------------------------------------------------
1852
- # KBTable.convert_to_string
1949
+ # KBTable.valid_index_type?
1853
1950
  #-----------------------------------------------------------------------
1854
1951
  #++
1855
- # Return value converted to String object.
1952
+ # Return true if valid index type.
1856
1953
  #
1857
- # *data_type*:: Symbol specifying data type.
1858
- # *value*:: Value to convert to String.
1954
+ # *field_type*:: Symbol specifying field type.
1859
1955
  #
1860
- def KBTable.convert_to_string(data_type, value)
1861
- case data_type
1862
- when :YAML
1863
- y = value.to_yaml
1864
- if y =~ ENCODE_RE
1865
- return y.gsub("&", '&amp;').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("&", '&amp;').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 Struct
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
- if @field_types.include?(:Memo)
2032
- input_rec.each_value { |r| r.write_to_file if r.is_a?(KBMemo) }
2033
- end
2034
-
2035
- if @field_types.include?(:Blob)
2036
- input_rec.each_value { |r| r.write_to_file if r.is_a?(KBBlob) }
2037
- end
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
- return @db.engine.insert_record(self, @field_names.zip(@field_types,
2042
- @field_defaults).collect do |fn, ft, fd|
2043
- if input_rec.has_key?(fn)
2044
- if input_rec[fn].nil?
2045
- if fd.nil?
2046
- ''
2047
- else
2048
- KBTable.convert_to_string(ft, fd)
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
- if fd.nil?
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
- update(*updates) { true }
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
- # Convert updates, which could be an array, a hash, or a Struct
2127
- # into a common format (i.e. hash).
2128
- update_rec = convert_input_data(data)
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
- updated_rec = {}
2148
- updated_rec[:rec] = \
2149
- @field_names.zip(@field_types).collect do |fn, ft|
2150
- KBTable.convert_to_string(ft,
2151
- update_rec.fetch(fn, rec.send(fn)))
2152
- end
2153
- updated_rec[:fpos] = rec.fpos
2154
- updated_rec[:line_length] = rec.line_length
2155
- updated_recs << updated_rec
2156
- end
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.engine.add_column(self, col_name, col_type, after)
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
- KBTable.convert_to_string(
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} = convert_to(:#{field_type}, s)
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} }.first
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} }.first
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
- if values.class == Proc
2849
- tbl_struct = Struct.new(*@field_names[1..-1])
2850
- tbl_rec = tbl_struct.new
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
- temp_hash = {}
2857
- @field_names[1..-1].collect { |f|
2990
+
2991
+ @field_names[1..-1].each do |f|
2858
2992
  temp_hash[f] = tbl_rec[f] unless tbl_rec[f].nil?
2859
- }
2860
- return temp_hash
2861
- elsif values[0].class.to_s == @record_class or \
2862
- values[0].class == @table_class
2863
- temp_hash = {}
2864
- @field_names[1..-1].collect { |f|
2865
- temp_hash[f] = values[0].send(f) if values[0].respond_to?(f)
2866
- }
2867
- return temp_hash
2868
- elsif values[0].class == Hash
2869
- return values[0].dup
2870
- elsif values[0].kind_of?(Struct)
2871
- temp_hash = {}
2872
- @field_names[1..-1].collect { |f|
2873
- temp_hash[f] = values[0][f] if values[0].members.include?(
2874
- f.to_s)
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
- temp_hash = {}
2889
- @field_names[1..-1].collect { |f|
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 unless data.has_key?(f)
3063
+ next if data[f].nil?
2909
3064
 
2910
- if data[f].nil?
2911
- raise 'A value for this field is required: %s' % f if \
2912
- @field_requireds[@field_names.index(f)]
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
- next unless select_cond.call(tbl_rec) unless select_cond.nil?
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 "Field name in select block not part of index!"
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 << convert_to(col_type, rec[col_pos])
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
- convert_to(col_type, rec[col_pos])
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
- # include DRb::DRbUndumped
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