kirbybaserubymanual.html in KirbyBase-2.6 vs kirbybaserubymanual.html in KirbyBase-2.6.1

- old
+ new

@@ -1,2324 +1,2324 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<meta name="generator" content="AsciiDoc 7.0.2" /> -<style type="text/css"> -/* Debug borders */ -p, li, dt, dd, div, pre, h1, h2, h3, h4, h5, h6 { -/* - border: 1px solid red; -*/ -} - -body { - margin: 1em 5% 1em 5%; -} - -a { color: blue; } -a:visited { color: fuchsia; } - -em { - font-style: italic; -} - -strong { - font-weight: bold; -} - -tt { - color: navy; -} - -h1, h2, h3, h4, h5, h6 { - color: #527bbd; - font-family: sans-serif; - margin-top: 1.2em; - margin-bottom: 0.5em; - line-height: 1.3; -} - -h1 { - border-bottom: 2px solid silver; -} -h2 { - border-bottom: 2px solid silver; - padding-top: 0.5em; -} - -div.sectionbody { - font-family: serif; - margin-left: 0; -} - -hr { - border: 1px solid silver; -} - -p { - margin-top: 0.5em; - margin-bottom: 0.5em; -} - -pre { - padding: 0; - margin: 0; -} - -span#author { - color: #527bbd; - font-family: sans-serif; - font-weight: bold; - font-size: 1.2em; -} -span#email { -} -span#revision { - font-family: sans-serif; -} - -div#footer { - font-family: sans-serif; - font-size: small; - border-top: 2px solid silver; - padding-top: 0.5em; - margin-top: 4.0em; -} -div#footer-text { - float: left; - padding-bottom: 0.5em; -} -div#footer-badges { - float: right; - padding-bottom: 0.5em; -} - -div#preamble, -div.tableblock, div.imageblock, div.exampleblock, div.verseblock, -div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock, -div.admonitionblock { - margin-right: 10%; - margin-top: 1.5em; - margin-bottom: 1.5em; -} -div.admonitionblock { - margin-top: 2.5em; - margin-bottom: 2.5em; -} - -div.content { /* Block element content. */ - padding: 0; -} - -/* Block element titles. */ -div.title, caption.title { - font-family: sans-serif; - font-weight: bold; - text-align: left; - margin-top: 1.0em; - margin-bottom: 0.5em; -} -div.title + * { - margin-top: 0; -} - -td div.title:first-child { - margin-top: 0.0em; -} -div.content div.title:first-child { - margin-top: 0.0em; -} -div.content + div.title { - margin-top: 0.0em; -} - -div.sidebarblock > div.content { - background: #ffffee; - border: 1px solid silver; - padding: 0.5em; -} - -div.listingblock > div.content { - border: 1px solid silver; - background: #f4f4f4; - padding: 0.5em; -} - -div.quoteblock > div.content { - padding-left: 2.0em; -} -div.quoteblock .attribution { - text-align: right; -} - -div.admonitionblock .icon { - vertical-align: top; - font-size: 1.1em; - font-weight: bold; - text-decoration: underline; - color: #527bbd; - padding-right: 0.5em; -} -div.admonitionblock td.content { - padding-left: 0.5em; - border-left: 2px solid silver; -} - -div.exampleblock > div.content { - border-left: 2px solid silver; - padding: 0.5em; -} - -div.verseblock div.content { - white-space: pre; -} - -div.imageblock div.content { padding-left: 0; } -div.imageblock img { border: 1px solid silver; } -span.image img { border-style: none; } - -dl { - margin-top: 0.8em; - margin-bottom: 0.8em; -} -dt { - margin-top: 0.5em; - margin-bottom: 0; - font-style: italic; -} -dd > *:first-child { - margin-top: 0; -} - -ul, ol { - list-style-position: outside; -} -ol.olist2 { - list-style-type: lower-alpha; -} - -div.tableblock > table { - border-color: #527bbd; - border-width: 3px; -} -thead { - font-family: sans-serif; - font-weight: bold; -} -tfoot { - font-weight: bold; -} - -div.hlist { - margin-top: 0.8em; - margin-bottom: 0.8em; -} -td.hlist1 { - vertical-align: top; - font-style: italic; - padding-right: 0.8em; -} -td.hlist2 { - vertical-align: top; -} - -@media print { - div#footer-badges { display: none; } -} -/* Workarounds for IE6's broken and incomplete CSS2. */ - -div.sidebar-content { - background: #ffffee; - border: 1px solid silver; - padding: 0.5em; -} -div.sidebar-title, div.image-title { - font-family: sans-serif; - font-weight: bold; - margin-top: 0.0em; - margin-bottom: 0.5em; -} - -div.listingblock div.content { - border: 1px solid silver; - background: #f4f4f4; - padding: 0.5em; -} - -div.quoteblock-content { - padding-left: 2.0em; -} - -div.exampleblock-content { - border-left: 2px solid silver; - padding-left: 0.5em; -} -</style> -<title>KirbyBase Manual (Ruby Version)</title> -</head> -<body> -<div id="header"> -<h1>KirbyBase Manual (Ruby Version)</h1> -<span id="author">Jamey Cribbs</span><br /> -<span id="email"><tt>&lt;<a href="mailto:jcribbs@netpromi.com">jcribbs@netpromi.com</a>&gt;</tt></span><br /> -v2.6 June 2006 -</div> -<div id="preamble"> -<div class="sectionbody"> -<div class="imageblock"> -<div class="content"> -<img src="images/kirby1.jpg" alt="images/kirby1.jpg"/> -</div> -<div class="image-title">Figure: Kirby, pictured here in attack mode.</div> -</div> -</div> -</div> -<h2>Table of Contents</h2> -<div class="sectionbody"> -<div class="sidebarblock"> -<div class="sidebar-content"> -<ol> -<li> -<p> -<a href="#introduction">Introduction</a> -</p> -</li> -<li> -<p> -<a href="#connecting-to-a-database">Connecting to a database</a> -</p> -</li> -<li> -<p> -<a href="#creating-a-new-table">Creating a new table</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#field-types">Database field types</a> -</p> -</li> -<li> -<p> -<a href="#recno">The recno field</a> -</p> -</li> -<li> -<p> -<a href="#encrypt">Turning on encryption</a> -</p> -</li> -<li> -<p> -<a href="#record-class">Specifying a custom record class</a> -</p> -</li> -<li> -<p> -<a href="#defaults">Specifying default values</a> -</p> -</li> -<li> -<p> -<a href="#requireds">Specifying required fields</a> -</p> -</li> -<li> -<p> -<a href="#creating-indexes">Indexes</a> -</p> -</li> -<li> -<p> -<a href="#a-note-about-indexes">A note about indexes</a> -</p> -</li> -<li> -<p> -<a href="#creating-lookup-fields">Lookup Fields</a> -</p> -</li> -<li> -<p> -<a href="#creating-link-many-fields">Link_many Fields</a> -</p> -</li> -<li> -<p> -<a href="#creating-calculated-fields">Calculated Fields</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#obtaining-a-reference-to-an-existing-table">Obtaining a reference to an existing table</a> -</p> -</li> -<li> -<p> -<a href="#insert-method">The insert method</a> -</p> -</li> -<li> -<p> -<a href="#how-to-select-records">How to select records</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#select-method">The select method</a> -</p> -</li> -<li> -<p> -<a href="#selecting-by-index">Selecting by index</a> -</p> -</li> -<li> -<p> -<a href="#selecting-by-recno-index">Selecting by :recno index</a> -</p> -</li> -<li> -<p> -<a href="#selects-involving-lookups-or-link-manys">Selects involving Lookups or Link_manys</a> -</p> -</li> -<li> -<p> -<a href="#a-note-about-nil-values">A note about nil values</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#kbresultset">KBResultSet</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#sorting-a-result-set">Sorting a result set</a> -</p> -</li> -<li> -<p> -<a href="#to-report">Producing a report from a result set</a> -</p> -</li> -<li> -<p> -<a href="#crosstabs">CrossTabs or Pivot Tables or Column Arrays (i.e. I don't know what to call them!)</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#how-to-update-records">How to update records</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#update-method">The update method</a> -</p> -</li> -<li> -<p> -<a href="#set-method">The set method</a> -</p> -</li> -<li> -<p> -<a href="#update-all-method">The update_all method</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#how-to-delete-records">How to delete records</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#delete-method">The delete method</a> -</p> -</li> -<li> -<p> -<a href="#clear-method">The clear method</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#pack-method">The pack method</a> -</p> -</li> -<li> -<p> -<a href="#memo-and-blob-fields">Memo and Blob Fields</a> -</p> -</li> -<li> -<p> -<a href="#misc-kirbybase-methods">Miscellaneous KirbyBase methods</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#drop-table">KirbyBase#drop_table</a> -</p> -</li> -<li> -<p> -<a href="#tables">KirbyBase#tables</a> -</p> -</li> -<li> -<p> -<a href="#table-exists">KirbyBase#table_exists?</a> -</p> -</li> -<li> -<p> -<a href="#rename-table">KirbyBase#rename_table</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#misc-kbtable-methods">Miscellaneous KBTable methods</a> -</p> -<ol class="olist2"> -<li> -<p> -<a href="#update-by-recno">KBTable#[]=</a> -</p> -</li> -<li> -<p> -<a href="#get-by-recno">KBTable#[]</a> -</p> -</li> -<li> -<p> -<a href="#field-names">KBTable#field_names</a> -</p> -</li> -<li> -<p> -<a href="#field-types">KBTable#field_types</a> -</p> -</li> -<li> -<p> -<a href="#total-recs">KBTable#total_recs</a> -</p> -</li> -<li> -<p> -<a href="#import-csv">KBTable#import_csv</a> -</p> -</li> -<li> -<p> -<a href="#add-column">KBTable#add_column</a> -</p> -</li> -<li> -<p> -<a href="#drop-column">KBTable#drop_column</a> -</p> -</li> -<li> -<p> -<a href="#rename-column">KBTable#rename_column</a> -</p> -</li> -<li> -<p> -<a href="#change-column-type">KBTable#change_column_type</a> -</p> -</li> -<li> -<p> -<a href="#change-column-default-value">KBTable#change_column_defaul_value</a> -</p> -</li> -<li> -<p> -<a href="#change-column-required">KBTable#change_column_required</a> -</p> -</li> -<li> -<p> -<a href="#add-index">KBTable#add_index</a> -</p> -</li> -<li> -<p> -<a href="#drop-index">KBTable#drop_index</a> -</p> -</li> -</ol> -</li> -<li> -<p> -<a href="#special-characters-in-data">Special characters in data</a> -</p> -</li> -<li> -<p> -<a href="#table-structure">Table Structure</a> -</p> -</li> -<li> -<p> -<a href="#server-notes">Server Notes</a> -</p> -</li> -<li> -<p> -<a href="#tips-on-improving-performance">Tips on improving performance</a> -</p> -</li> -<li> -<p> -<a href="#single-user-diagram">Single-user memory space diagram</a> -</p> -</li> -<li> -<p> -<a href="#client-server-diagram">Client/Server memory space diagram</a> -</p> -</li> -<li> -<p> -<a href="#license">License</a> -</p> -</li> -<li> -<p> -<a href="#credits">Credits</a> -</p> -</li> -</ol> -</div></div> -</div> -<h2><a id="introduction"></a>Introduction</h2> -<div class="sectionbody"> -<p>KirbyBase is a simple, pure-Ruby, flat-file database management system. -Some of its features include:</p> -<ul> -<li> -<p> -Since KirbyBase is written in Ruby, it runs anywhere that Ruby runs. It -is easy to distribute, since the entire DBMS is in one (approx. 100k) code -file. -</p> -</li> -<li> -<p> -All data is kept in plain-text, delimited files that can be edited by -hand. This gives you the ability to make changes by just opening the file -up in a text editor, or you can use another programming language to read the - file in and do things with it. -</p> -</li> -<li> -<p> -It can be used as an embedded database or in a client/server, multi-user -mode. To switch from one mode to the other, you only have to change one -line in your program. Included in the distribution is a sample database -server script using DRb. -</p> -</li> -<li> -<p> -Tables are kept on disk during use and accessed from disk when selecting, -updating, inserting, and deleting records. Changes to a table are written -immediately to disk. KirbyBase is not an "in-memory" database. Once you -update the database in your program, you can be assured that the change has -been saved to disk. The chance of lost data due to power interruptions, or -disk crashes is much reduced. Also, since the entire database does not have -to reside in memory at once, KirbyBase should run adequately on a -memory-constrained system. -</p> -</li> -<li> -<p> -You can specify the type of data that each field will hold. The available -data types are: String, Integer, Float, Boolean, Time, Date, DateTime, Memo, - Blob, and YAML. -</p> -</li> -<li> -<p> -The query syntax is very "rubyish". Instead of having to use another -language like SQL, you can express your query using Ruby code blocks. -</p> -</li> -<li> -<p> -All inserted records have an auto-incrementing primary key that is -guaranteed to uniquely identify the record throughout its lifetime. -</p> -</li> -<li> -<p> -You can specify that the result set be sorted on multiple fields, each -one either ascending or descending. -</p> -</li> -<li> -<p> -You can specify that certain fields be indexed. Using an index in a select -query can greatly improve performance on large tables (I've seen 10x speed -improvements). Index maintenance is completely handled by KirbyBase. -</p> -</li> -<li> -<p> -You can specify that a field has a "lookup table". Whenever that field is -accessed, the corresponding record from the lookup table is automatically -available. -</p> -</li> -<li> -<p> -You can specify one-to-many links between tables, somewhat analogous to a -"join" in SQL. -</p> -</li> -<li> -<p> -You can create calculated fields that are computed at runtime. -</p> -</li> -<li> -<p> -It is fairly fast, comparing favorably to SQLite. -</p> -</li> -</ul> -<p>In meeting your DBMS needs, KirbyBase fits in somewhere between plain -text files and small SQL database management systems like SQLite and -MySQL.</p> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="sidebar-title">Drop me a line!</div> -<p>If you find a use for KirbyBase, please send me an email telling how you -use it, whether it is ok for me to mention your application on the -"KirbyBase Applications" webpage, and possibly a link to your application's -webpage (if you have one).</p> -</div></div> -</div> -<h2><a id="connecting-to-a-database"></a>Connecting to a database</h2> -<div class="sectionbody"> -<p>To use Kirbybase, you first need to require the module:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>require 'kirbybase'</tt></pre> -</div></div> -<p>Then create an instance:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new</tt></pre> -</div></div> -<p>By default, the instance is a local connection using the same memory space -as your application. To specify a client/server connection, it would look -like this:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new(:client, 'localhost', 44444)</tt></pre> -</div></div> -<p>Of course, you would substitute your server's ip address and port number.</p> -<p>To specify a different location (other than the current directory) for the -database files, you need to pass the location as an argument, like so:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new(:local, nil, nil, './data')</tt></pre> -</div></div> -<p>KirbyBase treats every file in the specified directory that has the proper -extension as a database table. The default extension is ".tbl". To specify -a different extension, pass this as an argument, like so:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.dat')</tt></pre> -</div></div> -<p>To specify a different location other than the current directory for any -memo/blob files, you need to pass the location as an argument, like so:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.tbl', './memos')</tt></pre> -</div></div> -<p>If you don't want KirbyBase to spend time initially creating all of the -indexes for the tables in the database, you can pass true as the -delay_index_creation argument:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.tbl', './', true)</tt></pre> -</div></div> -<p>KirbyBase will skip initial index creation and will create a table's -indexes when the table is first referenced.</p> -<p>You can also specify the arguments via a code block. So, if you don't want -to have to specify a bunch of arguments just to get to the one you want, -put it in a code block attached to the method call. You could re-code the -previous example like so:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db = KirbyBase.new { |d| d.delay_index_creation = true }</tt></pre> -</div></div> -</div> -<h2><a id="creating-a-new-table"></a>Creating a new table</h2> -<div class="sectionbody"> -<p>To create a new table, you specify the table name, and a name and type for -each column. For example, to create a table containing information on World -War II planes:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl = db.create_table(:plane, :name, :String, :country, :String, -:role, :String, :speed, :Integer, :range, :Integer, :began_service, :Date, -:still_flying, :Boolean)</tt></pre> -</div></div> -<div class="sidebarblock"> -<a id="field-types"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KirbyBase Field Types</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/tip.png" alt="Tip" /> -</td> -<td class="content">:String, :Integer, :Float, :Boolean(true/false), :Time, :Date, -:DateTime, :Memo, :Blob, and :YAML.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="recno"></a> -<div class="sidebar-content"> -<div class="sidebar-title">The recno field</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">KirbyBase will automatically create a primary key field, called -recno, with a type of :Integer, for each table. This field will be -auto-incremented each time a record is inserted. You can use this field in -select, update, and delete queries, but you can't modify this field.</td> -</tr></table> -</div> -</div></div> -<h3><a id="encrypt"></a>Turning on encryption</h3> -<p>You can also specify whether the table should be encrypted. This will save -the table to disk encrypted (more like obfuscated) using a Vignere Cipher. -This is similar to rot13, but it uses a key to determine character -substitution. Still, this encryption will only stymie the most casual -of hackers. Do not rely on it to keep sensitive data safe! You specify -encryption by using a code block attached to #create_table:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl = db.create_table(:plane, :name, :String...) do |t| - t.encrypt = true -end</tt></pre> -</div></div> -<h3><a id="record-class"></a>Specifying a custom record class</h3> -<p>You can also specify that you want records of the table to be returned to -you as instances of a class. To do this, simply define a class before you -call #create_table. This class needs to have an accessor for each fieldname -in the table.</p> -<p>If this class has a class method, called #kb_create, KirbyBase, when -creating each record of the result set, will call that method and pass it -the field values of the result record. #kb_create will need to handle -creating an instance of the record class itself.</p> -<p>Here is an example of #kb_create in action:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>class Foobar - attr_accessor(:recno, :name, :country, :role, :speed, :range, - :began_service, :still_flying, :alpha, :beta) - - def Foobar.kb_create(recno, name, country, role, speed, range, - began_service, still_flying) - name ||= 'No Name!' - speed ||= 0 - began_service ||= Date.today - - Foobar.new do |x| - x.recno = recno - x.name = name - x.country = country - x.role = role - x.speed = speed - x.range = range - x.began_service = began_service - x.still_flying = still_flying - end - end - - def initialize(&amp;block) - instance_eval(&amp;block) - end -end</tt></pre> -</div></div> -<p>Pass this class to #create_table in an attached code block, like so:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl = db.create_table(:plane, :name, :String...) do |t| - t.record_class = Foobar -end</tt></pre> -</div></div> -<p>Now, when you call #select, the result set will be made up of instances of -Foobar, instead of the default, which is instances of Struct. This also -works the other way. You can now specify instances of Foobar as input to -the #insert, #update and #set methods. More on those methods below.</p> -<p>If the custom record class does not respond to #kb_create, KirbyBase will -call the class's #new method instead, passing it all of the field values.</p> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">The #create_table method will return a reference to a KBTable -instance. This is the only way, besides calling KirbyBase#get_table, that -you can obtain a handle to a KBTable instance. You can not call KBTable#new -directly.</td> -</tr></table> -</div> -</div></div> -<h3>Specifying Advanced Field Type Information</h3> -<p>There are four types of advanced field type information that you can -specify: - Defaults, Requireds, Indexes and Extras (i.e. Lookup, Link_many, - Calculated).</p> -<h4><a id="defaults"></a>Default Field Values</h4> -<p>Normally, when you insert a record, if you don't specify a value for a field -or specify nil as the value, KirbyBase stores this as an empty string -(i.e. "") in the table's physical file, and returns it as a nil value when -you do a #select.</p> -<p>However, you can tell KirbyBase that you want a column to have a default -value. Only KBTable#insert is affected by default values. Default values -do not apply to updating a record. So, for inserts, there are two cases -where a default value, if one is specified, will be applied: (1) if nil -is specified for a field's value, and (2) if no value is specified for a -field.</p> -<p>For example, to specify that the category column has a default value, you -would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:addressbook, :firstname, :String, - :lastname, :String, :phone_no, :String, - :category, {:DataType=&gt;:String, :Default=&gt;'Business'})</tt></pre> -</div></div> -<p>Notice that, since we are specifying advanced field type information, we -cannot just simply say :String for the second half of the field definition. -Instead, we have to pass a hash, with a :DataType item set to :String. -Next, because we are specifying a default value, we have to include a hash -item called :Default with its value set to whatever we want the default to -be. The default value must be of a type that is valid for the column.</p> -<h4><a id="requireds"></a>Required Fields</h4> -<p>Normally, when you insert or update a record, if you don't specify a value -for a field or specify nil as the value, KirbyBase stores this as an empty -string (i.e. "") in the table's physical file, and returns it as a nil -value when you do a #select.</p> -<p>However, you can tell KirbyBase that you want a column to be required -(i.e. you must supply a value and it can't be nil). When a record is -inserted or updated, an exception will be raised for any required field -that has not been given a value or been given a nil value.</p> -<p>For example, to specify that the category column is required, you would -code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:addressbook, :firstname, :String, - :lastname, :String, :phone_no, :String, - :category, {:DataType=&gt;:String, :Required=&gt;true})</tt></pre> -</div></div> -<p>Notice that, since we are specifying advanced field type information, we -cannot just simply say :String for the second half of the field definition. -Instead, we have to pass a hash, with a :DataType item set to :String. -Next, because we are specifying that a column is required, we have to -include a hash item called :Required with its value set to true.</p> -<h4><a id="creating-indexes"></a>Indexes</h4> -<p>Indexes are in-memory arrays that have an entry that corresponds to each -table record, but just holds the field values specified when the index was -created, plus the :recno of the actual table record. Because index arrays -are smaller than the actual table and because they are in-memory instead of -on-disk, using an index in a select query is usually much faster than -selecting against the table itself, especially when the table is quite -large.</p> -<p>To specify that an index is to be created, you need to tell KirbyBase which -fields are to be included in a particular index. You can have up to 5 -indexes per table. Indexes can either contain single or multiple fields. -For example, to create an index on firstname and lastname for a table called - :addressbook, you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:addressbook, :firstname, {:DataType=&gt;:String, :Index=&gt;1}, - :lastname, {:DataType=&gt;:String, :Index=&gt;1}, - :phone_no, :String)</tt></pre> -</div></div> -<p>Notice that, since we are specifying advanced field type information, we -cannot just simply say :String for the second half of the field definition. -Instead, we have to pass a hash, with a :DataType item set to :String. -Next, because we are creating an index, we have to include a hash item -called :Index with its value set from 1 to 5. For compound indexes, like -the one in the above example, we need to make sure that all fields in the -index have the same number. To have another index for a table, just make -sure you increment the index number. So, for example, if we wanted to have -another index for the :addressbook table on the field phone number, we would - code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:addressbook, :firstname, {:DataType=&gt;:String, :Index=&gt;1}, - :lastname, {:DataType=&gt;:String, :Index=&gt;1}, - :phone_no, {:DataType=&gt;:String, :Index=&gt;2})</tt></pre> -</div></div> -<p>Notice how we just increment the index number to 2 for the :phone_no index. -Since there are no other fields with the same index number, this will create - an index with just the :phone_no field in it. You will see how to use -indexes in your select queries later.</p> -<div class="sidebarblock"> -<a id="a-note-about-indexes"></a> -<div class="sidebar-content"> -<div class="sidebar-title">A note about indexes</div> -<p>When KirbyBase initializes, it creates an instance for each table in -the database. It also creates each index array for each indexed field in -each table. So, if there are several large tables that have indexed fields, -this database initialization process could take several seconds. I decided -to have it operate this way, because I thought that it made more sense to -have a user application wait once upon initialization, rather than have it -wait the first time a table is referenced. A user is more used to waiting -for an application to properly initialize, so this initial delay, I thought, -would feel more natural to the user, rather than a wait time in the middle -of the application.</p> -<p>If you find that this initial delay while indexes are created is -un-acceptable, you can pass delay_index_creation=true to KirbyBase.new. -This will bypass the initial index creation for all tables. Indexes for an -individual table will be created when the table is first referenced.</p> -<p>This is also an excellent reason to use the client/server capabilities of -KirbyBase. In client/server mode, the database initialization processing is -all done on the server, so, once this is done, any users starting their -client application and connecting to the server will experience no delay due -to index array creation.</p> -</div></div> -<h4><a id="creating-lookup-fields"></a>Lookup Fields</h4> -<p>Lookup fields are fields that hold a reference to a record in another table. - For example, say you have a table called :department that has fields called - :dept_id, :dept_name, and :manager. Now, let's say that you don't want the - :manager field to just hold the manager's name; you want it to point to the - manager's record in the :employee table, which has fields like - :employee_id, :firstname, :lastname, :ss_no, etc. What you want to do is - to tell KirbyBase that the :dept.manager field should actually point to the - manager's record in the :employee table (the "lookup" table). Here's how - you would do that:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:department, :dept_id, :String, :dept_name, :String, - :manager, {:DataType=&gt;:String, :Lookup=&gt;[:employee, :employee_id]})</tt></pre> -</div></div> -<p>Ok, what we have done here is to tell KirbyBase a little bit "extra" about -the :manager field. We are specifying that whenever we ask for the value of -:manager, we want KirbyBase to do a #select against the :employee table that -compares the value of the :department.manager field to the value of the -:employee.employee_id field. If an index is available for the -:employee.employee_id field, KirbyBase will automatically use it.</p> -<p>There is a shortcut to specifying a lookup field. If the :employee_id field - has been designated a "key" field for the :employee table, we can even - shorten our code and KirbyBase will figure it all out. For example, if the - :employee table was created with this code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:employee, :employee_id, {:DataType=&gt;:String, :Key=&gt;true}, - :firstname, :String, :lastname, :String)</tt></pre> -</div></div> -<p>Then the field definition for :manager could be re-written as:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:department, :dept_id, :String, :dept_name, :String, - :manager, :employee)</tt></pre> -</div></div> -<p>KirbyBase will figure out that you want to compare :department.manager to -:employee.employee_id.</p> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">In order to use the shortcut when specifying a lookup field, the -lookup table it is pointing to <strong>must</strong> already exist. This is so that -KirbyBase can do the defaulting magic.</td> -</tr></table> -</div> -</div></div> -<h4><a id="creating-link-many-fields"></a>Link_many Fields</h4> -<p>When you specify that a field has a Link_many, you are telling KirbyBase -that you want to create a one-to-many link between this field and a subset -of records from another table.</p> -<p>For example, say you are creating an order entry system for your business. -You have a master table called :orders that holds one record for each -customer order that is placed. It has fields like: :order_id, :cust_id, -:order_date, etc.</p> -<p>Now, you also need a table that is going to have a record for each detail -item type ordered. Let's call it :order_items and some of its fields would - be: :item_no, :qty, :order_id.</p> -<p>Notice that the detail table has a field called :order_id. This will hold -the order_id linking it back to the :orders.order_id table. If a customer -order's only one type of item, then there will only be one record in the -:order_items table that has that order_id. But, if the customer places an -order for many different types of items, there will be many records in the -:order_items table that have the same order_id. That's why we say that -there is a "one to many" link between the master (or parent) table, :orders, - and the detail (or child) table, :order_items.</p> -<p>When we select an :order record, we want the ability to also select all the -detail records from the :order_items table that belong to that order. We do - this by telling KirbyBase about the one-to-many link between the two -tables. Here's how:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:orders, :order_id, :Integer, :cust_id, :Integer, - :order_date, :Date, :items, {:DataType=&gt;:ResultSet, :Link_many=&gt;[:order_id, - :order_items, :order_id]}) - -db.create_table(:order_items, :item_no, :Integer, :qty, :Integer, - :order_id, :Integer)</tt></pre> -</div></div> -<p>Now, look at the :Link_many item in the field type definition hash in the -above example. We are specifying that a field, called :items, be created -with a field type of :ResultSet. We tell KirbyBase that, when we ask for -the value of :items, it should do a #select on :order_items and return a -KBResultSet that contains :order_items whose :order_id field (the last item -in the array above), equals the value of the :orders.order_id field (the -first item of the array above).</p> -<p>If you opened up the :orders table file in a text editor, you would notice -that, for each record, the :items field is blank. No data is ever stored in - this field, since its value is always computed at runtime.</p> -<h4><a id="creating-calculated-fields"></a>Calculated Fields</h4> -<p>When you specify that a field is a Calculated field, you are telling -KirbyBase to compute the value of the field at runtime, based on the code -string that you pass it at table creation time.</p> -<p>For example. let's say you have a table that is going to track your -purchases. It will have fields like :purchase_date, :description, :qty, and - :price.</p> -<p>Let's say that you want to have a "virtual" field, called :total_cost, that -is the result of quantity multiplied by price. You want this field -calculated at runtime, so that if you change a record's quantity or price, -the :total_cost field will automatically reflect the changes. -Here's how you would define this table:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:purchases, :purchase_date, :Date, :description, :String, - :qty, :Integer, :price, :Float, :total_cost, {:DataType=&gt;:Float, - :Calculated=&gt;'qty*price'})</tt></pre> -</div></div> -<p>See how we tell KirbyBase how to calculate the field? By multiplying the -:qty field by the :price field.</p> -<p>If you opened up the :purchases file in a text editor, you would notice -that, for each record, the :total_cost field is blank. No data is ever -stored in this field, since its value is always computed at runtime.</p> -</div> -<h2><a id="obtaining-a-reference-to-an-existing-table"></a>Obtaining a reference to an existing table</h2> -<div class="sectionbody"> -<p>If a table already exists and you need to get a reference to it so that you -can insert, select, etc., you would call the #get_table method from your -KirbyBase instance. This is how to obtain a handle to an existing table. -You cannot call KBTable#new directly.</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl_another_reference = db.get_table(:plane)</tt></pre> -</div></div> -<p>Then, you can use it just like you would use a reference you got when -you created a new table.</p> -</div> -<h2><a id="insert-method"></a>The insert method</h2> -<div class="sectionbody"> -<p>To insert records into a table, you use the insert method. You can use an -array, a hash, a struct, a code block, or an instance of the table's custom -record class to specify the insert values.</p> -<h3>Insert a record using an array for the input values:</h3> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.insert('FW-190', 'Germany', 'Fighter', 399, 499, - Date.new(1942,12,1), false)</tt></pre> -</div></div> -<p>The length of the data array must equal the number of columns in the table, -<strong>not</strong> including the :recno column. Also, the data types must match. In the -above example, specifying "399", instead of 399, would have resulted in an -error.</p> -<h3>Insert a record using a hash for the input values:</h3> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.insert(:name='P-51', :country='USA', :role='Fighter', :speed=403, - :range=1201, :began_service=Date.new(1943,6,24), :still_flying=true)</tt></pre> -</div></div> -<h3>Insert a record using a Struct for the input values:</h3> -<div class="listingblock"> -<div class="content"> -<pre><tt>InputRec = Struct.new(:name, :country, :role, :speed, :range, - :began_service, :still_flying) -rec = InputRec.new('P-47', 'USA', 'Fighter', 365, 888, Date.new(1943,3,12), -false) - -plane_tbl.insert(rec)</tt></pre> -</div></div> -<h3>Insert a record using a code block for the input values:</h3> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.insert do |r| - r.name = 'B-17' - r.country = 'USA' - r.role = 'Bomber' - r.speed = 315 - r.range = 1400 - r.began_service = Date.new(1937, 5, 1) - r.still_flying = true -end</tt></pre> -</div></div> -<h3>Insert a record using an instance of the table's custom record class:</h3> -<div class="listingblock"> -<div class="content"> -<pre><tt>foo = Foobar.new do |x| - x.name = 'Spitfire' - x.country = 'Great Britain' - x.role = 'Fighter' - x.speed = 333 - x.range = 454 - x.began_service = Date.new(1936, 1, 1) - x.still_flying = true - x.alpha = "This variable won't be stored in KirbyBase." - x.beta = 'Neither will this one.' -end - -plane_tbl.insert(foo)</tt></pre> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">The #insert method will return the record number of the newly created -record. This is an auto-incremented integer generated by KirbyBase. This -number will <strong>never</strong> change for the record and can be used as a unique -identifier for the record.</td> -</tr></table> -</div> -</div></div> -</div> -<h2><a id="how-to-select-records"></a>How to select records</h2> -<div class="sectionbody"> -<p>The syntax you use to select records to perform operations on is the same -for a select, update, or delete statement, so I wanted to cover, in -general, how to create a query expression first, before getting to the -specifics of select, update, and delete.</p> -<p>In KirbyBase, query conditions are specified simply by using Ruby code -blocks. Therefore, any code block that can be converted into a Proc object -is valid. This allows for great flexibility, as you will see in the many -examples below.</p> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/tip.png" alt="Tip" /> -</td> -<td class="content">You can find many examples of how to specify queries in the "examples" -directory of the KirbyBase distribution.</td> -</tr></table> -</div> -</div></div> -<p>Now that we have a general understanding of how to select records to operate -on, lets get more specific by looking at the select, update, and delete -methods.</p> -<h3><a id="select-method"></a>The select method</h3> -<p>The select method allows you to ask for all records in a table that match -certain selection criteria. Additionally, you can also specify which fields -you want included in the result set. The select method returns an instance -of KBResultSet, which is an array of records that satisfied the select -criteria. KBResultSet is covered in more detail later in the manual.</p> -<p>Here is the simplest example of a select statement:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select</tt></pre> -</div></div> -<p>Since, no code block was specified, KirbyBase will include all records in -the result set. Additionally, because a list of fields to include in the -result set was not specified, KirbyBase will include all fields for each -record in the result set.</p> -<p>To specify that you only want a subset of fields in the result set, you list - their field names as arguments to the select method. For example:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select(:name, :speed)</tt></pre> -</div></div> -<p>To specify selection criteria, attach a code block to the select method -call. For example, if you only wanted to select Japanese planes:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select(:name, :speed) { |r| r.country == 'Japan' }</tt></pre> -</div></div> -<p>You can combine multiple expressions in the code block. For example, to -select only US planes that have a speed greater than 350mph:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select { |r| r.country == 'USA' and r.speed &gt; 350 }</tt></pre> -</div></div> -<p>You can use regular expressions in the code block. Let's select all Axis -fighters:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select do |r| - r.country =~ /Germany|Japan/ and r.role == 'Fighter' -end</tt></pre> -</div></div> -<h3><a id="selecting-by-index"></a>Selecting by index</h3> -<p>Performing a select query using an index is almost identical to performing a - regular select query. You just have to specify the particular select -method, based on the index you wish to use.</p> -<p>For example, say you have created an index on the :speed field of the -:plane table. You want to search for all planes with a speed greater than -400 mph. Ruby automatically creates select methods for each one of the -indexes of a table. So, you would code your select query like this:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select_by_speed_index { |r| r.speed &gt; 400 }</tt></pre> -</div></div> -<p>Notice that you simply insert the name of the field as part of the method -name. Its as simple as that.</p> -<p>For compound indexes, you just need to specify the -indexed field names in the select method in the same order as they are in -the table. So, let's say you have indexed the :plane table on :country and -:role, in one index. To select on this compound index, you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select_by_country_role_index do |r| - r.country == 'Germany' and r.role == 'Fighter' } -end</tt></pre> -</div></div> -<p>Notice how you just list both fields as part of the name of the select -method, separated by underscores.</p> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/warning.png" alt="Warning" /> -</td> -<td class="content">If you specify a select method that does not exist, you will get an - error. Likewise, if you specify a query by an index, yet include a field -name in the query that is not part of the index, you will get an error.</td> -</tr></table> -</div> -</div></div> -<h3><a id="selecting-by-recno-index"></a>Selecting by :recno index</h3> -<p>For each table, a :recno index is automatically created, whether or not -other indexes are explicitly created by you. You can alway use this index -to select records based solely on :recno. For example:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select_by_recno_index { |r| [3, 45, 152].include?(r.recno) }</tt></pre> -</div></div> -<h3><a id="selects-involving-lookups-or-link-manys"></a>Selects Involving Lookups or Link_manys</h3> -<p>Selects that involve Lookup fields or Link_many fields have a special case -because both field types return complex objects rather than simple data -types. For example, consider the lookup field example that I described -earlier. For reference, here are the two table defintions again:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>department_tbl = db.create_table(:department, :dept_id, :String, - :dept_name, :String, :manager, {:DataType=&gt;:String, :Lookup=&gt;[:employee, - :employee_id]}) - -employee_tbl = db.create_table(:employee, :employee_id, {:DataType=&gt;:String, - :Key=&gt;true}, :firstname, :String, :lastname, :String)</tt></pre> -</div></div> -<p>To find the department managed by John Doe, the select query would look like - this:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>department_tbl.select do |r| - r.manager.firstname == 'John' and r.manager.lastname == 'Doe' -end</tt></pre> -</div></div> -<p>Notice how the manager attribute is itself a Struct with its own members.</p> -<p>To print out all departments including the manager's full name:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>department_tbl.select.each do |r| - puts 'Department: %s Manager: %s %s' % [r.dept_name, - r.manager.firstname, r.manager.lastname] -end</tt></pre> -</div></div> -<p>Selects involving Link_many fields are slightly different because they -involve ResultSets instead of just single objects. Here's the table -definitions from the earlier Link_many discussion:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>orders_tbl = db.create_table(:orders, :order_id, :Integer, - :cust_id, :Integer, :order_date, :Date, :items, {:DataType=&gt;:ResultSet, - :Link_many=&gt;[:order_id, :order_items, :order_id]}) - -order_items_tbl = db.create_table(:order_items, :item_no, :Integer, - :qty, :Integer, :order_id, :Integer)</tt></pre> -</div></div> -<p>To print an order and all of its associated detail items:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result = order_tbl.select { |r| r.order_id == 501 }.first -puts '%d %d %s' % [result.order_id, result.cust_id, result.order_date] - -result.items.each { |item| puts '%d %d' % [item.item_no, item.qty] }</tt></pre> -</div></div> -<p>Notice how the items attribute in the ResultSet is itself a ResultSet -containing all of the :order_items records that belong to the selected -order.</p> -<h3><a id="a-note-about-nil-values"></a>A Note About nil Values</h3> -<p>Beginning in version 2.6 of KirbyBase, nil values are no longer stored as -the singleton instance of NilClass in the database. Now, they are stored -as references to the singleton instance, kb_nil, of KBNilClass. KBNilClass -is as similar to NilClass as possible, but since NilClass cannot -be sub-classed, there are a few minor differences.</p> -<p>However, this should all be transparent to you because KirbyBase converts -kb_nil values to Ruby nil values before returning the results of a query. -The only time you will need to be worried about kb_nil is when you need to -explicitly test for a nil value in a query. For example:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select {|r| r.speed == kb_nil}</tt></pre> -</div></div> -<p>which is the same as:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select {|r| r.speed.kb_nil?}</tt></pre> -</div></div> -<p>Notice how it is very similar to how you would test for nil?</p> -<p>The only other difference you will now notice, is if you open up a table in -a text editor. Now, nil values are stored as "kb_nil", instead of being -stored as an empty string (i.e. ""). This has the added advantage that -KirbyBase can now distinguish between empty strings and nil values. In the -past, if you saved an empty string as a field value, the next time you -selected that record, KirbyBase would return that field's value as nil.</p> -<p>The main reason for making this change was to eliminate the need to -override NilClass#method_missing, which was cause for concern for some -users.</p> -</div> -<h2><a id="kbresultset"></a>KBResultSet</h2> -<div class="sectionbody"> -<p>As stated above, the select method returns an instance of KBResultSet, which -is an array of Struct objects (or instances of the class specified in -record_class), each one representing a record that satisfied the selection -criteria.</p> -<p>Since each item in KBResultSet is a Struct object, you can easily reference -its members using field names. So, to print the name and speed of each -German plane in the table you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select(:name, :speed) { |r| r.country == 'German' }.each do |r| - puts '%s %s' % [r.name, r.speed] -end</tt></pre> -</div></div> -<h3><a id="sorting-a-result-set"></a>Sorting a result set</h3> -<p>You can specify sort criteria by calling KBResultSet#sort. You must supply -the sort method with a list of field names that you want to sort by. For -example, to select all planes, include just name, country, and speed in the -result set, and sort the result set by country (ascending) then name -(ascending), you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result = plane_tbl.select(:name, :country, :speed).sort(:country, :name)</tt></pre> -</div></div> -<p>To indicate that you want a particular field sorted in descending order -rather than ascending order, you need to put a minus sign in front of it. -For example, to select all planes, include just name, country, and speed in -the result set, and sort the result set by country (ascending) then speed -(descending), you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result_set = plane_tbl.select(:name, :country, :speed).sort(:country, - -:speed)</tt></pre> -</div></div> -<p>You can also explicitly specify that a field be sorted in ascending order by -putting a plus sign in front of it. This is not necessary, since ascending -is the default, but its there if you prefer to use it.</p> -<h3><a id="to-report"></a>Producing a report from a result set</h3> -<p>Additionally, you can also transform the KBResultSet into a nicely formatted -report, suitable for printing, by calling KBResultSet#to_report. To print -a formatted report of all plane names, countries, and speeds, sorted by -name, you would code the following:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>puts plane_tbl.select(:name, :country, :speed).sort(:name).to_report</tt></pre> -</div></div> -<h3><a id="crosstabs"></a>CrossTabs or Pivot Tables or Column Arrays (i.e. I don't know what to call them!)</h3> -<p>Every KBResultSet has an additional feature that can prove quite useful. -When a result set is created, KirbyBase creates an array for each column -name that has all of the values of that column in it. Perhaps an example -would make this more clear. Let's say you have a table that looks like -this:</p> -<div class="tableblock"> -<table rules="all" -frame="border" -cellspacing="0" cellpadding="4"> -<col width="102" /> -<col width="114" /> -<col width="137" /> -<thead> - <tr> - <th align="left"> - name - </th> - <th align="left"> - speed - </th> - <th align="left"> - range - </th> - </tr> -</thead> -<tbody valign="top"> - <tr> - <td align="left"> - P-51 - </td> - <td align="left"> - 402 - </td> - <td align="left"> - 1201 - </td> - </tr> - <tr> - <td align="left"> - ME-109 - </td> - <td align="left"> - 354 - </td> - <td align="left"> - 544 - </td> - </tr> - <tr> - <td align="left"> - Spitfire - </td> - <td align="left"> - 343 - </td> - <td align="left"> - 501 - </td> - </tr> -</tbody> -</table> -</div> -<p>If you do a select on the table, not only will the result set contain a -row for each record that satisfied the select condition, but it will also -contain an array for each column, which will hold all the column values. -Here's an example, using the above mentioned table:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result = plane_tbl.select - -puts result[0].name =&gt; P-51 -puts result[0].speed =&gt; 402 - -p result.speed =&gt; [402,354,343]</tt></pre> -</div></div> -<p>Notice how you can reference this "column array" by calling the column name -as a method. KirbyBase returns an array that holds all the values, in this -case, of the speed column. This can be very useful in some cases. For a -good example of this, look in the examples\crosstab_test directory of the -distribution.</p> -</div> -<h2><a id="how-to-update-records"></a>How to update records</h2> -<div class="sectionbody"> -<p>You can update the data in a table either by using the KBTable#update method - by itself, or using it in conjunction with the KBResultSet#set method. -Both methods produce the same result. The main difference is that, while -using the #update method by itself, you can use a Hash, Array, or Struct as -your update criteria, using the #set method in conjunction with the #update -method adds the ability to use a code block as your update criteria. You -will see specific examples of this in "The update method" description below.</p> -<h3><a id="update-method"></a>The update method</h3> -<p>To update a table, you use the update method. You <strong>must</strong> specify a code -block that indicates which records are to be updated. Additionally, you must -specify the fields to be updated and the new values for those fields.</p> -<p>You can update records using a Hash, Struct, an Array, or an instance of a -class you defined. For example, to change the P-51's speed to 405mph and -its range to 1210 miles, you could code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.update(:speed=&gt;405, :range=&gt;1210) { |r| r.name == 'P-51' }</tt></pre> -</div></div> -<p>or:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>UpdateRec = Struct.new(:name, :country, :role, :speed, :range, - :began_service, :still_flying) - -rec = UpdateRec.new -rec.speed = 405 -rec.range = 1210 -plane_tbl.update(rec) { |r| r.name == 'P-51' }</tt></pre> -</div></div> -<h3><a id="set-method"></a>The set method</h3> -<p>You can also update records using a code block, via KBResultSet#set:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.update {|r| r.name == 'P-51'}.set do |r| - r.speed = 405 - r.range = 1210 -end</tt></pre> -</div></div> -<p>You can also update records using a Hash, Struct, or an Array, via -KBResultSet#set:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.update {|r| r.name == 'P-51'}.set(:speed=&gt;405, :range=&gt;1210)</tt></pre> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">When you don't supply a code block to the #select method, -KirbyBase automatically selects all of the records in the table. I felt -that having the #update method work the same way was too dangerous. It -would be too easy for someone to accidentally update all of the records in -a table if they forgot to supply a code block to #update. That is why the -#update method <strong>requires</strong> a code block. To update all of the records in a -table, use the #update_all method.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">The #update method will return an Integer specifying the number of -records that were updated.</td> -</tr></table> -</div> -</div></div> -<h3><a id="update-all-method"></a>The update_all method</h3> -<p>To update all records in a table, you can use KBTable#update_all. This -works just like the update method, except that you don't specify a code -block containing selection criteria.</p> -<p>For example, to add 50 mph to every record's speed field, you would code:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.update_all { |r| r.speed = r.speed + 50 }</tt></pre> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">The #update_all method will return an Integer specifying the number -of records that were updated.</td> -</tr></table> -</div> -</div></div> -</div> -<h2><a id="how-to-delete-records"></a>How to delete records</h2> -<div class="sectionbody"> -<p>Deleting records from a table is similar to performing a #select or an -#update.</p> -<h3><a id="delete-method"></a>The delete method</h3> -<p>To use the #delete method, you <strong>must</strong> supply a code block that identifies -which records should be deleted.</p> -<p>For example, to delete the FW-190's record from the table:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.delete { |r| r.name == 'FW-190' }</tt></pre> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">When you don't supply a code block to the #select method, -KirbyBase automatically selects all of the records in the table. I felt -that having the #delete method work the same way was too dangerous. It -would be too easy for someone to accidentally delete all of the records in -a table if they forgot to supply a code block to #delete. That is why the -#delete method <strong>requires</strong> a code block. To delete all of the records in a -table, use the #clear method.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">The #delete method will return an Integer specifying the total number -of records that were deleted.</td> -</tr></table> -</div> -</div></div> -<h3><a id="clear-method"></a>The clear (alias delete_all) method</h3> -<p>To completely empty a table, use the clear method. For example:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.clear</tt></pre> -</div></div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">By default, KirbyBase will reset the recno counter to 0. So, any -new records inserted after a #clear will be given a :recno starting with 1. -To prevent #clear from resetting the recno counter, pass false to #clear.</td> -</tr></table> -</div> -</div></div> -</div> -<h2><a id="pack-method"></a>The pack method</h2> -<div class="sectionbody"> -<p>When KirbyBase deletes a record, it really just fills the entire line in the -file with spaces. Deleting the entire line and moving each subsequent line -up one would take too much time. Also, when a record is updated, if the size -of the updated record is greater than the size of the old record, KirbyBase -spaces out that entire line in the file, and rewrites the updated record at -the end of the file. Again, this is done so that the entire file doesn't -have to be re-written just because one record got updated.</p> -<p>However, this means that after a lot of deletes and updates, a table can -have lots of blank lines in it. This slows down searches and makes the file -bigger than it has to be. You can use the pack method to remove these blank -lines:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>result = plane_tbl.pack</tt></pre> -</div></div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">You can only call this method when connect_type==:local.</td> -</tr></table> -</div> -<div class="sidebarblock"> -<div class="sidebar-content"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">The #pack method will return an Integer specifiying the number of -blank lines that were removed from the table.</td> -</tr></table> -</div> -</div></div> -</div> -<h2><a id="memo-and-blob-fields"></a>Memo and Blob Fields</h2> -<div class="sectionbody"> -<p>Memo and Blob fields operate a little differently from standard field types. -You specify their creation just like regular field types. Notice how you -can specify a base path for where memo/blob files will be stored.</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.create_table(:plane, :name, :String, :speed, :Integer, :descr, - :Memo) do |d| - d.memo_blob_path = './memos' -end</tt></pre> -</div></div> -<p>However, what you actually store in the Memo field upon an #insert is an -instance of KBMemo. KBMemo has two attributes: :filepath and :contents. -The first holds the path (including filename) to the text file that will -hold the contents of the memo. This path will be relative to whatever -path was specified as the memo_blob_path upon database initialization. Here - is an example of how to do this:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>memo_string = &lt;&lt;END_OF_STRING -The FW-190 was a World War II German fighter. It was used primarily as an -interceptor against Allied strategic bombers. -END_OF_STRING - -memo = KBMemo.new(db, 'FW-190.txt', memo_string) -plane_tbl.insert('FW-190', 'Germany', 399, 499, memo)</tt></pre> -</div></div> -<p>Updates work similarly in that you would need to supply a KBMemo instance -to the #update method for the :Memo field.</p> -<p>Other than this difference, you can use a memo field just like a regular -field. When you do a #select, KirbyBase goes out to the file that holds the -memo data, reads in all the lines, and returns a KBMemo instance. Here is -an example of how you can even query against a memo field:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.select { |r| r.descr.contents =~ /built in Detroit, Michigan/ }</tt></pre> -</div></div> -<p>And KirbyBase would select all records whose memo field contained the phrase - "built in Detroit, Michigan".</p> -<p>Blob fields work similarly, except that instead of doing a #readlines, -KirbyBase opens the file in binary mode and reads in the whole file at once.</p> -</div> -<h2><a id="misc-kirbybase-methods"></a>Miscellaneous KirbyBase methods</h2> -<div class="sectionbody"> -<div class="sidebarblock"> -<a id="drop-table"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KirbyBase#drop_table</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.drop_table(:plane)</tt></pre> -</div></div> -<p>Will delete a table from the database.</p> -<p>Returns true if table was dropped.</p> -</div></div> -<div class="sidebarblock"> -<a id="tables"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KirbyBase#tables</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.tables</tt></pre> -</div></div> -<p>Returns an array of all table names in the database.</p> -</div></div> -<div class="sidebarblock"> -<a id="table-exists"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KirbyBase#table_exists?</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.table_exists?(:plane)</tt></pre> -</div></div> -<p>Returns true if table exists, false otherwise.</p> -</div></div> -<div class="sidebarblock"> -<a id="rename-table"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KirbyBase#rename_table</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>db.rename_table(:plane, :warplanes)</tt></pre> -</div></div> -<p>Rename table to new name.</p> -<p>Returns a handle to the newly-renamed table.</p> -</div></div> -</div> -<h2><a id="misc-kbtable-methods"></a>Miscellaneous KBTable methods</h2> -<div class="sectionbody"> -<div class="sidebarblock"> -<a id="update-by-recno"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#[]=</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl[5] = {:country = 'Tasmania'}</tt></pre> -</div></div> -<p>You can quickly update an individual record by treating the table as a Hash -and the keys as recno's. You can update it using a Hash, Array, or Struct.</p> -<p>Returns Integer specifying number of records updated (should always be 1).</p> -</div></div> -<div class="sidebarblock"> -<a id="get-by-recno"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#[]</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl[5]</tt></pre> -</div></div> -<p>You can quickly select an individual record, by passing its recno to a -table as if it were a hash.</p> -<p>Returns a single record either in the form of a Struct or an instance of -the table's custom record class, if specified.</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl[5, 14, 33]</tt></pre> -</div></div> -<p>You can also use the [] method to get a group of records by passing it more -than one recno.</p> -<p>Returns a KBResultSet with the records having the recno's you passed in.</p> -</div></div> -<div class="sidebarblock"> -<a id="field-names"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#field_names</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.field_names</tt></pre> -</div></div> -<p>Returns an array of the table's field names.</p> -</div></div> -<div class="sidebarblock"> -<a id="field-types"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#field_types</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.field_types</tt></pre> -</div></div> -<p>Returns an array of the table's field types (i.e. :String, :Integer, :Float)</p> -</div></div> -<div class="sidebarblock"> -<a id="total-recs"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#total_recs</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.total_recs</tt></pre> -</div></div> -<p>Returns an Integer specifying the total number of records in the table.</p> -</div></div> -<div class="sidebarblock"> -<a id="import-csv"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#import_csv</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.import_csv(csv_filename)</tt></pre> -</div></div> -<p>This method allows you to import a csv file into the current table. -KirbyBase will attempt to convert the values in the csv file into their -corresponding KirbyBase field types, based upon the field types you -designated when you created the table.</p> -<p>If you have FasterCSV installed KirbyBase will use it instead of CSV from -the standard library.</p> -<p>Returns an Integer specifying the total number of records imported.</p> -</div></div> -<div class="sidebarblock"> -<a id="add-column"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#add_column</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.add_column(:weight, :Integer, :range)</tt></pre> -</div></div> -<p>This method allows you to add a column to an existing table. You must -specify a column name, and a column type. You can optionally specify a -column that you want the new column added after. If this is not specified, -KirbyBase will add the column to the end of the table.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #add_column changes the structure of a table, you can -only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="drop-column"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#drop_column</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.drop_column(:speed)</tt></pre> -</div></div> -<p>This method allows you to remove a column from an existing table. You must -specify the column name to be removed.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">You cannot drop the :recno column.</td> -</tr></table> -</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #drop_column changes the structure of a table, you can -only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="rename-column"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#rename_column</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.rename_column(:speed, :maximum_speed)</tt></pre> -</div></div> -<p>This method allows you to rename a column in an existing table. You must -specify the column to be renamed and a new column name.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">You cannot rename the :recno column.</td> -</tr></table> -</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #rename_column changes the structure of a table, you can -only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="change-column-type"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#change_column_type</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.change_column_type(:weight, :Float)</tt></pre> -</div></div> -<p>This method allows you to change a column's type in an existing table. You -must specify the column name and a new column type.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">You cannot change the type of the :recno column.</td> -</tr></table> -</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #change_column_type changes the structure of a table, you - can only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="change-column-default-value"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#change_column_default_value</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.change_column_default_value(:country, 'United States')</tt></pre> -</div></div> -<p>This method allows you to change a column's default value in an existing -table. You must specify the column name and a default value. If the -default value is equal to nil, this, in effect will make the column not have -a default value any more.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Since the :recno column cannot have a default value, you cannot -change the default value of the :recno column.</td> -</tr></table> -</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #change_column_default_value changes the structure of a -table, you can only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="change-column-required"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#change_column_required</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.change_column_required(:country, true)</tt></pre> -</div></div> -<p>This method allows you to change whether a value for a column is required or -not. You must specify the column name and either true or false for the -required argument.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">You cannot specify whether the :recno column is required or not.</td> -</tr></table> -</div> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #change_column_required changes the structure of a table, - you can only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="add-index"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#add_index</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.add_index(:name, :country)</tt></pre> -</div></div> -<p>This method allows you to add an index to an existing table. This index can - consist of one or more columns. You must specify one or more column names -that you want to make up the index.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #add_index changes the structure of a table, you can -only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -<div class="sidebarblock"> -<a id="drop-index"></a> -<div class="sidebar-content"> -<div class="sidebar-title">KBTable#drop_index</div> -<div class="listingblock"> -<div class="content"> -<pre><tt>plane_tbl.drop_index(:name, :country)</tt></pre> -</div></div> -<p>This method allows you to drop an index from an existing table. You must -specify one or more column names that make up the index that you want to -drop.</p> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/important.png" alt="Important" /> -</td> -<td class="content">Because #drop_index changes the structure of a table, you can -only call this method when connect_type==:local.</td> -</tr></table> -</div> -</div></div> -</div> -<h2><a id="special-characters-in-data"></a>Special characters in data</h2> -<div class="sectionbody"> -<p>Since KirbyBase tables are just plain-text, newline-delimited files with -each field delimited by a <em>|</em>, certain ASCII characters could cause problems -when used as input. For example, entering a newline character (\n on Unix, -\r\n on Windows) as part of a record's data would cause problems later when -KirbyBase attempted to read this record. Likewise, using the <em>|</em> character -in your data would also cause problems as KirbyBase uses this character as a - field delimiter. Finally, it turns out that Python has problems handling -octal code \032 under Windows (possibly because it equates to Ctrl-Z), so -to keep compatibility between the Ruby and Python versions of KirbyBase, -this issue needs to be handled.</p> -<p>To handle the above special characters as data input, KirbyBase checks all -:String and :YAML input data and replaces the special characters with -encodings that are safe. The following table shows how replacements are -done:</p> -<div class="tableblock"> -<table rules="all" -frame="border" -cellspacing="0" cellpadding="4"> -<col width="182" /> -<col width="240" /> -<thead> - <tr> - <th align="left"> - Input Character - </th> - <th align="left"> - KirbyBase Replacement - </th> - </tr> -</thead> -<tbody valign="top"> - <tr> - <td align="left"> - \n - </td> - <td align="left"> - &amp;amp;linefeed; - </td> - </tr> - <tr> - <td align="left"> - \r - </td> - <td align="left"> - &amp;amp;carriage_return; - </td> - </tr> - <tr> - <td align="left"> - \032 - </td> - <td align="left"> - &amp;amp;substitute; - </td> - </tr> - <tr> - <td align="left"> - | - </td> - <td align="left"> - &amp;amp;pipe; - </td> - </tr> - <tr> - <td align="left"> - &amp; - </td> - <td align="left"> - &amp;amp; - </td> - </tr> -</tbody> -</table> -</div> -<p>KirbyBase will translate to and from the special characters as data is -written to and read from a table. It should all be transparent to the user. -The only time you would encounter the replacement words is if you were to -open up the physical table file in a text editor or read it in as input -outside of KirbyBase.</p> -</div> -<h2><a id="table-structure"></a>Table Structure</h2> -<div class="sectionbody"> -<p>Every table in KirbyBase is represented by a physical, newline-delimited -text-file. Here is an example:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>000006|000000|Struct|recno:Integer|name:String|country:String|speed:Integer -1|P-51|USA|403 -2|P-51|USA|365 -3|Sptitfire|England|345 -4|Oscar|Japan|361 -5|ME-109|Germany|366 -6|Zero|Japan|377</tt></pre> -</div></div> -<p>The first line is the header rec. Each field is delimited by a "|". The -first field in the header is the record counter. It is incremented by -KirbyBase to create new record numbers when records are inserted.</p> -<p>The second field in the header is the deleted-records counter. Every time a -line in the file is blanked-out (see <a href="#pack-method">The pack method</a>), this -number is incremented. You can use this field in a maintenance script so -that the table is packed whenever the deleted-records counter reaches, say, -5,000 records.</p> -<p>The third field in the header is the record_class field. If you specified a -class when you created the table, its name will show up here and records -returned from a #select will be instances of this class. The default is -"Struct".</p> -<p>The fourth field in the header is the :recno field. This field is -automatically added to the table when it is created. The field name and -field type are separated by a ":".</p> -<p>The rest of the fields are whatever you specified when you created the -table.</p> -<p>If there is a Z in the first position of the header rec and the rest of the -file is a bunch of random characters, this means that the table is -encrypted.</p> -<p>Each record after the header record is simply a line of text. Newline -characters delimit records.</p> -</div> -<h2><a id="server-notes"></a>Server Notes</h2> -<div class="sectionbody"> -<p>There is a server script included in the distribution called kbserver.rb. -This script uses DRb to turn KirbyBase into a client/server, multi-user -DBMS. This DBMS server handles table locking for you so you don't have to -worry about it.</p> -</div> -<h2><a id="tips-on-improving-performance"></a>Tips on improving performance</h2> -<div class="sectionbody"> -<h3>Beware of Date/DateTime</h3> -<p>Converting a String (the format in which data is stored in a KirbyBase -table) to a Date/DateTime is slow. If you have a large table with a -Date/DateTime field, this can result in long query times.</p> -<p>To get around this, you can specify the field type as a :String, instead of -a :Date/:DateTime. Queries will still work correctly, because Date/DateTime - fields that are in String format sort the same way they would if they were -in native format. Here's an example. The following two expressions will -result in the same result set being returned:</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>date_field &lt;= Date.new(2005, 05, 11)</tt></pre> -</div></div> -<p>and</p> -<div class="listingblock"> -<div class="content"> -<pre><tt>date_field_stored_as_string_field &lt;= "2005-05-11"</tt></pre> -</div></div> -<h3>Create indexes on large tables</h3> -<p>The larger a table becomes, the more sense it makes to create an index on -one or more of its fields. Even though you take a hit when KirbyBase first - initializes because it has to create the index arrays, you make up for it -after that in greatly improved query speeds. I have noticed speed-ups of -as much as 10x on large tables.</p> -<h3>Create indexes on foreign keys</h3> -<p>If you have a Link_many on a table field, you might want to think about -creating an index on the field in the child table that is being linked to. -When performing a one-to-many link, KirbyBase will automatically take -advantage of an index on the link field in the child table.</p> -<p>By the way, the same holds true for Lookups.</p> -<h3>When possible, always search by :recno</h3> -<p>This might be a no-brainer, but, if you have the chance to select by -:recno, use the built-in #select_by_recno_index method (or the #[] method). -This is even faster than selecting on a regularly indexed field, because the - :recno index that KirbyBase creates for each table is actually a Hash, not -an Array like all of the regular indexes. So selects are even faster.</p> -</div> -<h2><a id="single-user-diagram"></a>Single-user memory space diagram</h2> -<div class="sectionbody"> -<div class="imageblock"> -<div class="content"> -<img src="images/single_user.png" alt="images/single_user.png"/> -</div> -<div class="image-title">Figure: Single-user (embedded) mode.</div> -</div> -</div> -<h2><a id="client-server-diagram"></a>Client/Server memory space diagram</h2> -<div class="sectionbody"> -<div class="imageblock"> -<div class="content"> -<img src="images/client_server.png" alt="images/client_server.png"/> -</div> -<div class="image-title">Figure: Client/Server (separate memory spaces) mode.</div> -</div> -</div> -<h2><a id="license"></a>License</h2> -<div class="sectionbody"> -<p>KirbyBase is distributed under the same license terms as Ruby.</p> -</div> -<h2><a id="credits"></a>Credits</h2> -<div class="sectionbody"> -<div class="admonitionblock"> -<table><tr> -<td class="icon"> -<img src="./images/note.png" alt="Note" /> -</td> -<td class="content">This manual was produced using the awesome text document formatter, -<a href="http://www.methods.co.nz/asciidoc/">AsciiDoc</a>.</td> -</tr></table> -</div> -</div> -<div id="footer"> -<div id="footer-text"> -Last updated 26-Jun-2006 14:36:38 EDT -</div> -</div> -</body> -</html> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<meta name="generator" content="AsciiDoc 7.0.2" /> +<style type="text/css"> +/* Debug borders */ +p, li, dt, dd, div, pre, h1, h2, h3, h4, h5, h6 { +/* + border: 1px solid red; +*/ +} + +body { + margin: 1em 5% 1em 5%; +} + +a { color: blue; } +a:visited { color: fuchsia; } + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +tt { + color: navy; +} + +h1, h2, h3, h4, h5, h6 { + color: #527bbd; + font-family: sans-serif; + margin-top: 1.2em; + margin-bottom: 0.5em; + line-height: 1.3; +} + +h1 { + border-bottom: 2px solid silver; +} +h2 { + border-bottom: 2px solid silver; + padding-top: 0.5em; +} + +div.sectionbody { + font-family: serif; + margin-left: 0; +} + +hr { + border: 1px solid silver; +} + +p { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +pre { + padding: 0; + margin: 0; +} + +span#author { + color: #527bbd; + font-family: sans-serif; + font-weight: bold; + font-size: 1.2em; +} +span#email { +} +span#revision { + font-family: sans-serif; +} + +div#footer { + font-family: sans-serif; + font-size: small; + border-top: 2px solid silver; + padding-top: 0.5em; + margin-top: 4.0em; +} +div#footer-text { + float: left; + padding-bottom: 0.5em; +} +div#footer-badges { + float: right; + padding-bottom: 0.5em; +} + +div#preamble, +div.tableblock, div.imageblock, div.exampleblock, div.verseblock, +div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock, +div.admonitionblock { + margin-right: 10%; + margin-top: 1.5em; + margin-bottom: 1.5em; +} +div.admonitionblock { + margin-top: 2.5em; + margin-bottom: 2.5em; +} + +div.content { /* Block element content. */ + padding: 0; +} + +/* Block element titles. */ +div.title, caption.title { + font-family: sans-serif; + font-weight: bold; + text-align: left; + margin-top: 1.0em; + margin-bottom: 0.5em; +} +div.title + * { + margin-top: 0; +} + +td div.title:first-child { + margin-top: 0.0em; +} +div.content div.title:first-child { + margin-top: 0.0em; +} +div.content + div.title { + margin-top: 0.0em; +} + +div.sidebarblock > div.content { + background: #ffffee; + border: 1px solid silver; + padding: 0.5em; +} + +div.listingblock > div.content { + border: 1px solid silver; + background: #f4f4f4; + padding: 0.5em; +} + +div.quoteblock > div.content { + padding-left: 2.0em; +} +div.quoteblock .attribution { + text-align: right; +} + +div.admonitionblock .icon { + vertical-align: top; + font-size: 1.1em; + font-weight: bold; + text-decoration: underline; + color: #527bbd; + padding-right: 0.5em; +} +div.admonitionblock td.content { + padding-left: 0.5em; + border-left: 2px solid silver; +} + +div.exampleblock > div.content { + border-left: 2px solid silver; + padding: 0.5em; +} + +div.verseblock div.content { + white-space: pre; +} + +div.imageblock div.content { padding-left: 0; } +div.imageblock img { border: 1px solid silver; } +span.image img { border-style: none; } + +dl { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +dt { + margin-top: 0.5em; + margin-bottom: 0; + font-style: italic; +} +dd > *:first-child { + margin-top: 0; +} + +ul, ol { + list-style-position: outside; +} +ol.olist2 { + list-style-type: lower-alpha; +} + +div.tableblock > table { + border-color: #527bbd; + border-width: 3px; +} +thead { + font-family: sans-serif; + font-weight: bold; +} +tfoot { + font-weight: bold; +} + +div.hlist { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +td.hlist1 { + vertical-align: top; + font-style: italic; + padding-right: 0.8em; +} +td.hlist2 { + vertical-align: top; +} + +@media print { + div#footer-badges { display: none; } +} +/* Workarounds for IE6's broken and incomplete CSS2. */ + +div.sidebar-content { + background: #ffffee; + border: 1px solid silver; + padding: 0.5em; +} +div.sidebar-title, div.image-title { + font-family: sans-serif; + font-weight: bold; + margin-top: 0.0em; + margin-bottom: 0.5em; +} + +div.listingblock div.content { + border: 1px solid silver; + background: #f4f4f4; + padding: 0.5em; +} + +div.quoteblock-content { + padding-left: 2.0em; +} + +div.exampleblock-content { + border-left: 2px solid silver; + padding-left: 0.5em; +} +</style> +<title>KirbyBase Manual (Ruby Version)</title> +</head> +<body> +<div id="header"> +<h1>KirbyBase Manual (Ruby Version)</h1> +<span id="author">Jamey Cribbs</span><br /> +<span id="email"><tt>&lt;<a href="mailto:jcribbs@netpromi.com">jcribbs@netpromi.com</a>&gt;</tt></span><br /> +v2.6 June 2006 +</div> +<div id="preamble"> +<div class="sectionbody"> +<div class="imageblock"> +<div class="content"> +<img src="images/kirby1.jpg" alt="images/kirby1.jpg"/> +</div> +<div class="image-title">Figure: Kirby, pictured here in attack mode.</div> +</div> +</div> +</div> +<h2>Table of Contents</h2> +<div class="sectionbody"> +<div class="sidebarblock"> +<div class="sidebar-content"> +<ol> +<li> +<p> +<a href="#introduction">Introduction</a> +</p> +</li> +<li> +<p> +<a href="#connecting-to-a-database">Connecting to a database</a> +</p> +</li> +<li> +<p> +<a href="#creating-a-new-table">Creating a new table</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#field-types">Database field types</a> +</p> +</li> +<li> +<p> +<a href="#recno">The recno field</a> +</p> +</li> +<li> +<p> +<a href="#encrypt">Turning on encryption</a> +</p> +</li> +<li> +<p> +<a href="#record-class">Specifying a custom record class</a> +</p> +</li> +<li> +<p> +<a href="#defaults">Specifying default values</a> +</p> +</li> +<li> +<p> +<a href="#requireds">Specifying required fields</a> +</p> +</li> +<li> +<p> +<a href="#creating-indexes">Indexes</a> +</p> +</li> +<li> +<p> +<a href="#a-note-about-indexes">A note about indexes</a> +</p> +</li> +<li> +<p> +<a href="#creating-lookup-fields">Lookup Fields</a> +</p> +</li> +<li> +<p> +<a href="#creating-link-many-fields">Link_many Fields</a> +</p> +</li> +<li> +<p> +<a href="#creating-calculated-fields">Calculated Fields</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#obtaining-a-reference-to-an-existing-table">Obtaining a reference to an existing table</a> +</p> +</li> +<li> +<p> +<a href="#insert-method">The insert method</a> +</p> +</li> +<li> +<p> +<a href="#how-to-select-records">How to select records</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#select-method">The select method</a> +</p> +</li> +<li> +<p> +<a href="#selecting-by-index">Selecting by index</a> +</p> +</li> +<li> +<p> +<a href="#selecting-by-recno-index">Selecting by :recno index</a> +</p> +</li> +<li> +<p> +<a href="#selects-involving-lookups-or-link-manys">Selects involving Lookups or Link_manys</a> +</p> +</li> +<li> +<p> +<a href="#a-note-about-nil-values">A note about nil values</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#kbresultset">KBResultSet</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#sorting-a-result-set">Sorting a result set</a> +</p> +</li> +<li> +<p> +<a href="#to-report">Producing a report from a result set</a> +</p> +</li> +<li> +<p> +<a href="#crosstabs">CrossTabs or Pivot Tables or Column Arrays (i.e. I don't know what to call them!)</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#how-to-update-records">How to update records</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#update-method">The update method</a> +</p> +</li> +<li> +<p> +<a href="#set-method">The set method</a> +</p> +</li> +<li> +<p> +<a href="#update-all-method">The update_all method</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#how-to-delete-records">How to delete records</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#delete-method">The delete method</a> +</p> +</li> +<li> +<p> +<a href="#clear-method">The clear method</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#pack-method">The pack method</a> +</p> +</li> +<li> +<p> +<a href="#memo-and-blob-fields">Memo and Blob Fields</a> +</p> +</li> +<li> +<p> +<a href="#misc-kirbybase-methods">Miscellaneous KirbyBase methods</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#drop-table">KirbyBase#drop_table</a> +</p> +</li> +<li> +<p> +<a href="#tables">KirbyBase#tables</a> +</p> +</li> +<li> +<p> +<a href="#table-exists">KirbyBase#table_exists?</a> +</p> +</li> +<li> +<p> +<a href="#rename-table">KirbyBase#rename_table</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#misc-kbtable-methods">Miscellaneous KBTable methods</a> +</p> +<ol class="olist2"> +<li> +<p> +<a href="#update-by-recno">KBTable#[]=</a> +</p> +</li> +<li> +<p> +<a href="#get-by-recno">KBTable#[]</a> +</p> +</li> +<li> +<p> +<a href="#field-names">KBTable#field_names</a> +</p> +</li> +<li> +<p> +<a href="#field-types">KBTable#field_types</a> +</p> +</li> +<li> +<p> +<a href="#total-recs">KBTable#total_recs</a> +</p> +</li> +<li> +<p> +<a href="#import-csv">KBTable#import_csv</a> +</p> +</li> +<li> +<p> +<a href="#add-column">KBTable#add_column</a> +</p> +</li> +<li> +<p> +<a href="#drop-column">KBTable#drop_column</a> +</p> +</li> +<li> +<p> +<a href="#rename-column">KBTable#rename_column</a> +</p> +</li> +<li> +<p> +<a href="#change-column-type">KBTable#change_column_type</a> +</p> +</li> +<li> +<p> +<a href="#change-column-default-value">KBTable#change_column_defaul_value</a> +</p> +</li> +<li> +<p> +<a href="#change-column-required">KBTable#change_column_required</a> +</p> +</li> +<li> +<p> +<a href="#add-index">KBTable#add_index</a> +</p> +</li> +<li> +<p> +<a href="#drop-index">KBTable#drop_index</a> +</p> +</li> +</ol> +</li> +<li> +<p> +<a href="#special-characters-in-data">Special characters in data</a> +</p> +</li> +<li> +<p> +<a href="#table-structure">Table Structure</a> +</p> +</li> +<li> +<p> +<a href="#server-notes">Server Notes</a> +</p> +</li> +<li> +<p> +<a href="#tips-on-improving-performance">Tips on improving performance</a> +</p> +</li> +<li> +<p> +<a href="#single-user-diagram">Single-user memory space diagram</a> +</p> +</li> +<li> +<p> +<a href="#client-server-diagram">Client/Server memory space diagram</a> +</p> +</li> +<li> +<p> +<a href="#license">License</a> +</p> +</li> +<li> +<p> +<a href="#credits">Credits</a> +</p> +</li> +</ol> +</div></div> +</div> +<h2><a id="introduction"></a>Introduction</h2> +<div class="sectionbody"> +<p>KirbyBase is a simple, pure-Ruby, flat-file database management system. +Some of its features include:</p> +<ul> +<li> +<p> +Since KirbyBase is written in Ruby, it runs anywhere that Ruby runs. It +is easy to distribute, since the entire DBMS is in one (approx. 100k) code +file. +</p> +</li> +<li> +<p> +All data is kept in plain-text, delimited files that can be edited by +hand. This gives you the ability to make changes by just opening the file +up in a text editor, or you can use another programming language to read the + file in and do things with it. +</p> +</li> +<li> +<p> +It can be used as an embedded database or in a client/server, multi-user +mode. To switch from one mode to the other, you only have to change one +line in your program. Included in the distribution is a sample database +server script using DRb. +</p> +</li> +<li> +<p> +Tables are kept on disk during use and accessed from disk when selecting, +updating, inserting, and deleting records. Changes to a table are written +immediately to disk. KirbyBase is not an "in-memory" database. Once you +update the database in your program, you can be assured that the change has +been saved to disk. The chance of lost data due to power interruptions, or +disk crashes is much reduced. Also, since the entire database does not have +to reside in memory at once, KirbyBase should run adequately on a +memory-constrained system. +</p> +</li> +<li> +<p> +You can specify the type of data that each field will hold. The available +data types are: String, Integer, Float, Boolean, Time, Date, DateTime, Memo, + Blob, and YAML. +</p> +</li> +<li> +<p> +The query syntax is very "rubyish". Instead of having to use another +language like SQL, you can express your query using Ruby code blocks. +</p> +</li> +<li> +<p> +All inserted records have an auto-incrementing primary key that is +guaranteed to uniquely identify the record throughout its lifetime. +</p> +</li> +<li> +<p> +You can specify that the result set be sorted on multiple fields, each +one either ascending or descending. +</p> +</li> +<li> +<p> +You can specify that certain fields be indexed. Using an index in a select +query can greatly improve performance on large tables (I've seen 10x speed +improvements). Index maintenance is completely handled by KirbyBase. +</p> +</li> +<li> +<p> +You can specify that a field has a "lookup table". Whenever that field is +accessed, the corresponding record from the lookup table is automatically +available. +</p> +</li> +<li> +<p> +You can specify one-to-many links between tables, somewhat analogous to a +"join" in SQL. +</p> +</li> +<li> +<p> +You can create calculated fields that are computed at runtime. +</p> +</li> +<li> +<p> +It is fairly fast, comparing favorably to SQLite. +</p> +</li> +</ul> +<p>In meeting your DBMS needs, KirbyBase fits in somewhere between plain +text files and small SQL database management systems like SQLite and +MySQL.</p> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="sidebar-title">Drop me a line!</div> +<p>If you find a use for KirbyBase, please send me an email telling how you +use it, whether it is ok for me to mention your application on the +"KirbyBase Applications" webpage, and possibly a link to your application's +webpage (if you have one).</p> +</div></div> +</div> +<h2><a id="connecting-to-a-database"></a>Connecting to a database</h2> +<div class="sectionbody"> +<p>To use Kirbybase, you first need to require the module:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>require 'kirbybase'</tt></pre> +</div></div> +<p>Then create an instance:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new</tt></pre> +</div></div> +<p>By default, the instance is a local connection using the same memory space +as your application. To specify a client/server connection, it would look +like this:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new(:client, 'localhost', 44444)</tt></pre> +</div></div> +<p>Of course, you would substitute your server's ip address and port number.</p> +<p>To specify a different location (other than the current directory) for the +database files, you need to pass the location as an argument, like so:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new(:local, nil, nil, './data')</tt></pre> +</div></div> +<p>KirbyBase treats every file in the specified directory that has the proper +extension as a database table. The default extension is ".tbl". To specify +a different extension, pass this as an argument, like so:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.dat')</tt></pre> +</div></div> +<p>To specify a different location other than the current directory for any +memo/blob files, you need to pass the location as an argument, like so:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.tbl', './memos')</tt></pre> +</div></div> +<p>If you don't want KirbyBase to spend time initially creating all of the +indexes for the tables in the database, you can pass true as the +delay_index_creation argument:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new(:local, nil, nil, './', '.tbl', './', true)</tt></pre> +</div></div> +<p>KirbyBase will skip initial index creation and will create a table's +indexes when the table is first referenced.</p> +<p>You can also specify the arguments via a code block. So, if you don't want +to have to specify a bunch of arguments just to get to the one you want, +put it in a code block attached to the method call. You could re-code the +previous example like so:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db = KirbyBase.new { |d| d.delay_index_creation = true }</tt></pre> +</div></div> +</div> +<h2><a id="creating-a-new-table"></a>Creating a new table</h2> +<div class="sectionbody"> +<p>To create a new table, you specify the table name, and a name and type for +each column. For example, to create a table containing information on World +War II planes:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl = db.create_table(:plane, :name, :String, :country, :String, +:role, :String, :speed, :Integer, :range, :Integer, :began_service, :Date, +:still_flying, :Boolean)</tt></pre> +</div></div> +<div class="sidebarblock"> +<a id="field-types"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KirbyBase Field Types</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/tip.png" alt="Tip" /> +</td> +<td class="content">:String, :Integer, :Float, :Boolean(true/false), :Time, :Date, +:DateTime, :Memo, :Blob, and :YAML.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="recno"></a> +<div class="sidebar-content"> +<div class="sidebar-title">The recno field</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">KirbyBase will automatically create a primary key field, called +recno, with a type of :Integer, for each table. This field will be +auto-incremented each time a record is inserted. You can use this field in +select, update, and delete queries, but you can't modify this field.</td> +</tr></table> +</div> +</div></div> +<h3><a id="encrypt"></a>Turning on encryption</h3> +<p>You can also specify whether the table should be encrypted. This will save +the table to disk encrypted (more like obfuscated) using a Vignere Cipher. +This is similar to rot13, but it uses a key to determine character +substitution. Still, this encryption will only stymie the most casual +of hackers. Do not rely on it to keep sensitive data safe! You specify +encryption by using a code block attached to #create_table:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl = db.create_table(:plane, :name, :String...) do |t| + t.encrypt = true +end</tt></pre> +</div></div> +<h3><a id="record-class"></a>Specifying a custom record class</h3> +<p>You can also specify that you want records of the table to be returned to +you as instances of a class. To do this, simply define a class before you +call #create_table. This class needs to have an accessor for each fieldname +in the table.</p> +<p>If this class has a class method, called #kb_create, KirbyBase, when +creating each record of the result set, will call that method and pass it +the field values of the result record. #kb_create will need to handle +creating an instance of the record class itself.</p> +<p>Here is an example of #kb_create in action:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>class Foobar + attr_accessor(:recno, :name, :country, :role, :speed, :range, + :began_service, :still_flying, :alpha, :beta) + + def Foobar.kb_create(recno, name, country, role, speed, range, + began_service, still_flying) + name ||= 'No Name!' + speed ||= 0 + began_service ||= Date.today + + Foobar.new do |x| + x.recno = recno + x.name = name + x.country = country + x.role = role + x.speed = speed + x.range = range + x.began_service = began_service + x.still_flying = still_flying + end + end + + def initialize(&amp;block) + instance_eval(&amp;block) + end +end</tt></pre> +</div></div> +<p>Pass this class to #create_table in an attached code block, like so:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl = db.create_table(:plane, :name, :String...) do |t| + t.record_class = Foobar +end</tt></pre> +</div></div> +<p>Now, when you call #select, the result set will be made up of instances of +Foobar, instead of the default, which is instances of Struct. This also +works the other way. You can now specify instances of Foobar as input to +the #insert, #update and #set methods. More on those methods below.</p> +<p>If the custom record class does not respond to #kb_create, KirbyBase will +call the class's #new method instead, passing it all of the field values.</p> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">The #create_table method will return a reference to a KBTable +instance. This is the only way, besides calling KirbyBase#get_table, that +you can obtain a handle to a KBTable instance. You can not call KBTable#new +directly.</td> +</tr></table> +</div> +</div></div> +<h3>Specifying Advanced Field Type Information</h3> +<p>There are four types of advanced field type information that you can +specify: + Defaults, Requireds, Indexes and Extras (i.e. Lookup, Link_many, + Calculated).</p> +<h4><a id="defaults"></a>Default Field Values</h4> +<p>Normally, when you insert a record, if you don't specify a value for a field +or specify nil as the value, KirbyBase stores this as an empty string +(i.e. "") in the table's physical file, and returns it as a nil value when +you do a #select.</p> +<p>However, you can tell KirbyBase that you want a column to have a default +value. Only KBTable#insert is affected by default values. Default values +do not apply to updating a record. So, for inserts, there are two cases +where a default value, if one is specified, will be applied: (1) if nil +is specified for a field's value, and (2) if no value is specified for a +field.</p> +<p>For example, to specify that the category column has a default value, you +would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:addressbook, :firstname, :String, + :lastname, :String, :phone_no, :String, + :category, {:DataType=&gt;:String, :Default=&gt;'Business'})</tt></pre> +</div></div> +<p>Notice that, since we are specifying advanced field type information, we +cannot just simply say :String for the second half of the field definition. +Instead, we have to pass a hash, with a :DataType item set to :String. +Next, because we are specifying a default value, we have to include a hash +item called :Default with its value set to whatever we want the default to +be. The default value must be of a type that is valid for the column.</p> +<h4><a id="requireds"></a>Required Fields</h4> +<p>Normally, when you insert or update a record, if you don't specify a value +for a field or specify nil as the value, KirbyBase stores this as an empty +string (i.e. "") in the table's physical file, and returns it as a nil +value when you do a #select.</p> +<p>However, you can tell KirbyBase that you want a column to be required +(i.e. you must supply a value and it can't be nil). When a record is +inserted or updated, an exception will be raised for any required field +that has not been given a value or been given a nil value.</p> +<p>For example, to specify that the category column is required, you would +code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:addressbook, :firstname, :String, + :lastname, :String, :phone_no, :String, + :category, {:DataType=&gt;:String, :Required=&gt;true})</tt></pre> +</div></div> +<p>Notice that, since we are specifying advanced field type information, we +cannot just simply say :String for the second half of the field definition. +Instead, we have to pass a hash, with a :DataType item set to :String. +Next, because we are specifying that a column is required, we have to +include a hash item called :Required with its value set to true.</p> +<h4><a id="creating-indexes"></a>Indexes</h4> +<p>Indexes are in-memory arrays that have an entry that corresponds to each +table record, but just holds the field values specified when the index was +created, plus the :recno of the actual table record. Because index arrays +are smaller than the actual table and because they are in-memory instead of +on-disk, using an index in a select query is usually much faster than +selecting against the table itself, especially when the table is quite +large.</p> +<p>To specify that an index is to be created, you need to tell KirbyBase which +fields are to be included in a particular index. You can have up to 5 +indexes per table. Indexes can either contain single or multiple fields. +For example, to create an index on firstname and lastname for a table called + :addressbook, you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:addressbook, :firstname, {:DataType=&gt;:String, :Index=&gt;1}, + :lastname, {:DataType=&gt;:String, :Index=&gt;1}, + :phone_no, :String)</tt></pre> +</div></div> +<p>Notice that, since we are specifying advanced field type information, we +cannot just simply say :String for the second half of the field definition. +Instead, we have to pass a hash, with a :DataType item set to :String. +Next, because we are creating an index, we have to include a hash item +called :Index with its value set from 1 to 5. For compound indexes, like +the one in the above example, we need to make sure that all fields in the +index have the same number. To have another index for a table, just make +sure you increment the index number. So, for example, if we wanted to have +another index for the :addressbook table on the field phone number, we would + code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:addressbook, :firstname, {:DataType=&gt;:String, :Index=&gt;1}, + :lastname, {:DataType=&gt;:String, :Index=&gt;1}, + :phone_no, {:DataType=&gt;:String, :Index=&gt;2})</tt></pre> +</div></div> +<p>Notice how we just increment the index number to 2 for the :phone_no index. +Since there are no other fields with the same index number, this will create + an index with just the :phone_no field in it. You will see how to use +indexes in your select queries later.</p> +<div class="sidebarblock"> +<a id="a-note-about-indexes"></a> +<div class="sidebar-content"> +<div class="sidebar-title">A note about indexes</div> +<p>When KirbyBase initializes, it creates an instance for each table in +the database. It also creates each index array for each indexed field in +each table. So, if there are several large tables that have indexed fields, +this database initialization process could take several seconds. I decided +to have it operate this way, because I thought that it made more sense to +have a user application wait once upon initialization, rather than have it +wait the first time a table is referenced. A user is more used to waiting +for an application to properly initialize, so this initial delay, I thought, +would feel more natural to the user, rather than a wait time in the middle +of the application.</p> +<p>If you find that this initial delay while indexes are created is +un-acceptable, you can pass delay_index_creation=true to KirbyBase.new. +This will bypass the initial index creation for all tables. Indexes for an +individual table will be created when the table is first referenced.</p> +<p>This is also an excellent reason to use the client/server capabilities of +KirbyBase. In client/server mode, the database initialization processing is +all done on the server, so, once this is done, any users starting their +client application and connecting to the server will experience no delay due +to index array creation.</p> +</div></div> +<h4><a id="creating-lookup-fields"></a>Lookup Fields</h4> +<p>Lookup fields are fields that hold a reference to a record in another table. + For example, say you have a table called :department that has fields called + :dept_id, :dept_name, and :manager. Now, let's say that you don't want the + :manager field to just hold the manager's name; you want it to point to the + manager's record in the :employee table, which has fields like + :employee_id, :firstname, :lastname, :ss_no, etc. What you want to do is + to tell KirbyBase that the :dept.manager field should actually point to the + manager's record in the :employee table (the "lookup" table). Here's how + you would do that:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:department, :dept_id, :String, :dept_name, :String, + :manager, {:DataType=&gt;:String, :Lookup=&gt;[:employee, :employee_id]})</tt></pre> +</div></div> +<p>Ok, what we have done here is to tell KirbyBase a little bit "extra" about +the :manager field. We are specifying that whenever we ask for the value of +:manager, we want KirbyBase to do a #select against the :employee table that +compares the value of the :department.manager field to the value of the +:employee.employee_id field. If an index is available for the +:employee.employee_id field, KirbyBase will automatically use it.</p> +<p>There is a shortcut to specifying a lookup field. If the :employee_id field + has been designated a "key" field for the :employee table, we can even + shorten our code and KirbyBase will figure it all out. For example, if the + :employee table was created with this code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:employee, :employee_id, {:DataType=&gt;:String, :Key=&gt;true}, + :firstname, :String, :lastname, :String)</tt></pre> +</div></div> +<p>Then the field definition for :manager could be re-written as:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:department, :dept_id, :String, :dept_name, :String, + :manager, :employee)</tt></pre> +</div></div> +<p>KirbyBase will figure out that you want to compare :department.manager to +:employee.employee_id.</p> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">In order to use the shortcut when specifying a lookup field, the +lookup table it is pointing to <strong>must</strong> already exist. This is so that +KirbyBase can do the defaulting magic.</td> +</tr></table> +</div> +</div></div> +<h4><a id="creating-link-many-fields"></a>Link_many Fields</h4> +<p>When you specify that a field has a Link_many, you are telling KirbyBase +that you want to create a one-to-many link between this field and a subset +of records from another table.</p> +<p>For example, say you are creating an order entry system for your business. +You have a master table called :orders that holds one record for each +customer order that is placed. It has fields like: :order_id, :cust_id, +:order_date, etc.</p> +<p>Now, you also need a table that is going to have a record for each detail +item type ordered. Let's call it :order_items and some of its fields would + be: :item_no, :qty, :order_id.</p> +<p>Notice that the detail table has a field called :order_id. This will hold +the order_id linking it back to the :orders.order_id table. If a customer +order's only one type of item, then there will only be one record in the +:order_items table that has that order_id. But, if the customer places an +order for many different types of items, there will be many records in the +:order_items table that have the same order_id. That's why we say that +there is a "one to many" link between the master (or parent) table, :orders, + and the detail (or child) table, :order_items.</p> +<p>When we select an :order record, we want the ability to also select all the +detail records from the :order_items table that belong to that order. We do + this by telling KirbyBase about the one-to-many link between the two +tables. Here's how:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:orders, :order_id, :Integer, :cust_id, :Integer, + :order_date, :Date, :items, {:DataType=&gt;:ResultSet, :Link_many=&gt;[:order_id, + :order_items, :order_id]}) + +db.create_table(:order_items, :item_no, :Integer, :qty, :Integer, + :order_id, :Integer)</tt></pre> +</div></div> +<p>Now, look at the :Link_many item in the field type definition hash in the +above example. We are specifying that a field, called :items, be created +with a field type of :ResultSet. We tell KirbyBase that, when we ask for +the value of :items, it should do a #select on :order_items and return a +KBResultSet that contains :order_items whose :order_id field (the last item +in the array above), equals the value of the :orders.order_id field (the +first item of the array above).</p> +<p>If you opened up the :orders table file in a text editor, you would notice +that, for each record, the :items field is blank. No data is ever stored in + this field, since its value is always computed at runtime.</p> +<h4><a id="creating-calculated-fields"></a>Calculated Fields</h4> +<p>When you specify that a field is a Calculated field, you are telling +KirbyBase to compute the value of the field at runtime, based on the code +string that you pass it at table creation time.</p> +<p>For example. let's say you have a table that is going to track your +purchases. It will have fields like :purchase_date, :description, :qty, and + :price.</p> +<p>Let's say that you want to have a "virtual" field, called :total_cost, that +is the result of quantity multiplied by price. You want this field +calculated at runtime, so that if you change a record's quantity or price, +the :total_cost field will automatically reflect the changes. +Here's how you would define this table:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:purchases, :purchase_date, :Date, :description, :String, + :qty, :Integer, :price, :Float, :total_cost, {:DataType=&gt;:Float, + :Calculated=&gt;'qty*price'})</tt></pre> +</div></div> +<p>See how we tell KirbyBase how to calculate the field? By multiplying the +:qty field by the :price field.</p> +<p>If you opened up the :purchases file in a text editor, you would notice +that, for each record, the :total_cost field is blank. No data is ever +stored in this field, since its value is always computed at runtime.</p> +</div> +<h2><a id="obtaining-a-reference-to-an-existing-table"></a>Obtaining a reference to an existing table</h2> +<div class="sectionbody"> +<p>If a table already exists and you need to get a reference to it so that you +can insert, select, etc., you would call the #get_table method from your +KirbyBase instance. This is how to obtain a handle to an existing table. +You cannot call KBTable#new directly.</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl_another_reference = db.get_table(:plane)</tt></pre> +</div></div> +<p>Then, you can use it just like you would use a reference you got when +you created a new table.</p> +</div> +<h2><a id="insert-method"></a>The insert method</h2> +<div class="sectionbody"> +<p>To insert records into a table, you use the insert method. You can use an +array, a hash, a struct, a code block, or an instance of the table's custom +record class to specify the insert values.</p> +<h3>Insert a record using an array for the input values:</h3> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.insert('FW-190', 'Germany', 'Fighter', 399, 499, + Date.new(1942,12,1), false)</tt></pre> +</div></div> +<p>The length of the data array must equal the number of columns in the table, +<strong>not</strong> including the :recno column. Also, the data types must match. In the +above example, specifying "399", instead of 399, would have resulted in an +error.</p> +<h3>Insert a record using a hash for the input values:</h3> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.insert(:name='P-51', :country='USA', :role='Fighter', :speed=403, + :range=1201, :began_service=Date.new(1943,6,24), :still_flying=true)</tt></pre> +</div></div> +<h3>Insert a record using a Struct for the input values:</h3> +<div class="listingblock"> +<div class="content"> +<pre><tt>InputRec = Struct.new(:name, :country, :role, :speed, :range, + :began_service, :still_flying) +rec = InputRec.new('P-47', 'USA', 'Fighter', 365, 888, Date.new(1943,3,12), +false) + +plane_tbl.insert(rec)</tt></pre> +</div></div> +<h3>Insert a record using a code block for the input values:</h3> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.insert do |r| + r.name = 'B-17' + r.country = 'USA' + r.role = 'Bomber' + r.speed = 315 + r.range = 1400 + r.began_service = Date.new(1937, 5, 1) + r.still_flying = true +end</tt></pre> +</div></div> +<h3>Insert a record using an instance of the table's custom record class:</h3> +<div class="listingblock"> +<div class="content"> +<pre><tt>foo = Foobar.new do |x| + x.name = 'Spitfire' + x.country = 'Great Britain' + x.role = 'Fighter' + x.speed = 333 + x.range = 454 + x.began_service = Date.new(1936, 1, 1) + x.still_flying = true + x.alpha = "This variable won't be stored in KirbyBase." + x.beta = 'Neither will this one.' +end + +plane_tbl.insert(foo)</tt></pre> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">The #insert method will return the record number of the newly created +record. This is an auto-incremented integer generated by KirbyBase. This +number will <strong>never</strong> change for the record and can be used as a unique +identifier for the record.</td> +</tr></table> +</div> +</div></div> +</div> +<h2><a id="how-to-select-records"></a>How to select records</h2> +<div class="sectionbody"> +<p>The syntax you use to select records to perform operations on is the same +for a select, update, or delete statement, so I wanted to cover, in +general, how to create a query expression first, before getting to the +specifics of select, update, and delete.</p> +<p>In KirbyBase, query conditions are specified simply by using Ruby code +blocks. Therefore, any code block that can be converted into a Proc object +is valid. This allows for great flexibility, as you will see in the many +examples below.</p> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/tip.png" alt="Tip" /> +</td> +<td class="content">You can find many examples of how to specify queries in the "examples" +directory of the KirbyBase distribution.</td> +</tr></table> +</div> +</div></div> +<p>Now that we have a general understanding of how to select records to operate +on, lets get more specific by looking at the select, update, and delete +methods.</p> +<h3><a id="select-method"></a>The select method</h3> +<p>The select method allows you to ask for all records in a table that match +certain selection criteria. Additionally, you can also specify which fields +you want included in the result set. The select method returns an instance +of KBResultSet, which is an array of records that satisfied the select +criteria. KBResultSet is covered in more detail later in the manual.</p> +<p>Here is the simplest example of a select statement:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select</tt></pre> +</div></div> +<p>Since, no code block was specified, KirbyBase will include all records in +the result set. Additionally, because a list of fields to include in the +result set was not specified, KirbyBase will include all fields for each +record in the result set.</p> +<p>To specify that you only want a subset of fields in the result set, you list + their field names as arguments to the select method. For example:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select(:name, :speed)</tt></pre> +</div></div> +<p>To specify selection criteria, attach a code block to the select method +call. For example, if you only wanted to select Japanese planes:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select(:name, :speed) { |r| r.country == 'Japan' }</tt></pre> +</div></div> +<p>You can combine multiple expressions in the code block. For example, to +select only US planes that have a speed greater than 350mph:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select { |r| r.country == 'USA' and r.speed &gt; 350 }</tt></pre> +</div></div> +<p>You can use regular expressions in the code block. Let's select all Axis +fighters:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select do |r| + r.country =~ /Germany|Japan/ and r.role == 'Fighter' +end</tt></pre> +</div></div> +<h3><a id="selecting-by-index"></a>Selecting by index</h3> +<p>Performing a select query using an index is almost identical to performing a + regular select query. You just have to specify the particular select +method, based on the index you wish to use.</p> +<p>For example, say you have created an index on the :speed field of the +:plane table. You want to search for all planes with a speed greater than +400 mph. Ruby automatically creates select methods for each one of the +indexes of a table. So, you would code your select query like this:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select_by_speed_index { |r| r.speed &gt; 400 }</tt></pre> +</div></div> +<p>Notice that you simply insert the name of the field as part of the method +name. Its as simple as that.</p> +<p>For compound indexes, you just need to specify the +indexed field names in the select method in the same order as they are in +the table. So, let's say you have indexed the :plane table on :country and +:role, in one index. To select on this compound index, you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select_by_country_role_index do |r| + r.country == 'Germany' and r.role == 'Fighter' } +end</tt></pre> +</div></div> +<p>Notice how you just list both fields as part of the name of the select +method, separated by underscores.</p> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/warning.png" alt="Warning" /> +</td> +<td class="content">If you specify a select method that does not exist, you will get an + error. Likewise, if you specify a query by an index, yet include a field +name in the query that is not part of the index, you will get an error.</td> +</tr></table> +</div> +</div></div> +<h3><a id="selecting-by-recno-index"></a>Selecting by :recno index</h3> +<p>For each table, a :recno index is automatically created, whether or not +other indexes are explicitly created by you. You can alway use this index +to select records based solely on :recno. For example:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select_by_recno_index { |r| [3, 45, 152].include?(r.recno) }</tt></pre> +</div></div> +<h3><a id="selects-involving-lookups-or-link-manys"></a>Selects Involving Lookups or Link_manys</h3> +<p>Selects that involve Lookup fields or Link_many fields have a special case +because both field types return complex objects rather than simple data +types. For example, consider the lookup field example that I described +earlier. For reference, here are the two table defintions again:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>department_tbl = db.create_table(:department, :dept_id, :String, + :dept_name, :String, :manager, {:DataType=&gt;:String, :Lookup=&gt;[:employee, + :employee_id]}) + +employee_tbl = db.create_table(:employee, :employee_id, {:DataType=&gt;:String, + :Key=&gt;true}, :firstname, :String, :lastname, :String)</tt></pre> +</div></div> +<p>To find the department managed by John Doe, the select query would look like + this:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>department_tbl.select do |r| + r.manager.firstname == 'John' and r.manager.lastname == 'Doe' +end</tt></pre> +</div></div> +<p>Notice how the manager attribute is itself a Struct with its own members.</p> +<p>To print out all departments including the manager's full name:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>department_tbl.select.each do |r| + puts 'Department: %s Manager: %s %s' % [r.dept_name, + r.manager.firstname, r.manager.lastname] +end</tt></pre> +</div></div> +<p>Selects involving Link_many fields are slightly different because they +involve ResultSets instead of just single objects. Here's the table +definitions from the earlier Link_many discussion:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>orders_tbl = db.create_table(:orders, :order_id, :Integer, + :cust_id, :Integer, :order_date, :Date, :items, {:DataType=&gt;:ResultSet, + :Link_many=&gt;[:order_id, :order_items, :order_id]}) + +order_items_tbl = db.create_table(:order_items, :item_no, :Integer, + :qty, :Integer, :order_id, :Integer)</tt></pre> +</div></div> +<p>To print an order and all of its associated detail items:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result = order_tbl.select { |r| r.order_id == 501 }.first +puts '%d %d %s' % [result.order_id, result.cust_id, result.order_date] + +result.items.each { |item| puts '%d %d' % [item.item_no, item.qty] }</tt></pre> +</div></div> +<p>Notice how the items attribute in the ResultSet is itself a ResultSet +containing all of the :order_items records that belong to the selected +order.</p> +<h3><a id="a-note-about-nil-values"></a>A Note About nil Values</h3> +<p>Beginning in version 2.6 of KirbyBase, nil values are no longer stored as +the singleton instance of NilClass in the database. Now, they are stored +as references to the singleton instance, kb_nil, of KBNilClass. KBNilClass +is as similar to NilClass as possible, but since NilClass cannot +be sub-classed, there are a few minor differences.</p> +<p>However, this should all be transparent to you because KirbyBase converts +kb_nil values to Ruby nil values before returning the results of a query. +The only time you will need to be worried about kb_nil is when you need to +explicitly test for a nil value in a query. For example:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select {|r| r.speed == kb_nil}</tt></pre> +</div></div> +<p>which is the same as:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select {|r| r.speed.kb_nil?}</tt></pre> +</div></div> +<p>Notice how it is very similar to how you would test for nil?</p> +<p>The only other difference you will now notice, is if you open up a table in +a text editor. Now, nil values are stored as "kb_nil", instead of being +stored as an empty string (i.e. ""). This has the added advantage that +KirbyBase can now distinguish between empty strings and nil values. In the +past, if you saved an empty string as a field value, the next time you +selected that record, KirbyBase would return that field's value as nil.</p> +<p>The main reason for making this change was to eliminate the need to +override NilClass#method_missing, which was cause for concern for some +users.</p> +</div> +<h2><a id="kbresultset"></a>KBResultSet</h2> +<div class="sectionbody"> +<p>As stated above, the select method returns an instance of KBResultSet, which +is an array of Struct objects (or instances of the class specified in +record_class), each one representing a record that satisfied the selection +criteria.</p> +<p>Since each item in KBResultSet is a Struct object, you can easily reference +its members using field names. So, to print the name and speed of each +German plane in the table you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select(:name, :speed) { |r| r.country == 'German' }.each do |r| + puts '%s %s' % [r.name, r.speed] +end</tt></pre> +</div></div> +<h3><a id="sorting-a-result-set"></a>Sorting a result set</h3> +<p>You can specify sort criteria by calling KBResultSet#sort. You must supply +the sort method with a list of field names that you want to sort by. For +example, to select all planes, include just name, country, and speed in the +result set, and sort the result set by country (ascending) then name +(ascending), you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result = plane_tbl.select(:name, :country, :speed).sort(:country, :name)</tt></pre> +</div></div> +<p>To indicate that you want a particular field sorted in descending order +rather than ascending order, you need to put a minus sign in front of it. +For example, to select all planes, include just name, country, and speed in +the result set, and sort the result set by country (ascending) then speed +(descending), you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result_set = plane_tbl.select(:name, :country, :speed).sort(:country, + -:speed)</tt></pre> +</div></div> +<p>You can also explicitly specify that a field be sorted in ascending order by +putting a plus sign in front of it. This is not necessary, since ascending +is the default, but its there if you prefer to use it.</p> +<h3><a id="to-report"></a>Producing a report from a result set</h3> +<p>Additionally, you can also transform the KBResultSet into a nicely formatted +report, suitable for printing, by calling KBResultSet#to_report. To print +a formatted report of all plane names, countries, and speeds, sorted by +name, you would code the following:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>puts plane_tbl.select(:name, :country, :speed).sort(:name).to_report</tt></pre> +</div></div> +<h3><a id="crosstabs"></a>CrossTabs or Pivot Tables or Column Arrays (i.e. I don't know what to call them!)</h3> +<p>Every KBResultSet has an additional feature that can prove quite useful. +When a result set is created, KirbyBase creates an array for each column +name that has all of the values of that column in it. Perhaps an example +would make this more clear. Let's say you have a table that looks like +this:</p> +<div class="tableblock"> +<table rules="all" +frame="border" +cellspacing="0" cellpadding="4"> +<col width="102" /> +<col width="114" /> +<col width="137" /> +<thead> + <tr> + <th align="left"> + name + </th> + <th align="left"> + speed + </th> + <th align="left"> + range + </th> + </tr> +</thead> +<tbody valign="top"> + <tr> + <td align="left"> + P-51 + </td> + <td align="left"> + 402 + </td> + <td align="left"> + 1201 + </td> + </tr> + <tr> + <td align="left"> + ME-109 + </td> + <td align="left"> + 354 + </td> + <td align="left"> + 544 + </td> + </tr> + <tr> + <td align="left"> + Spitfire + </td> + <td align="left"> + 343 + </td> + <td align="left"> + 501 + </td> + </tr> +</tbody> +</table> +</div> +<p>If you do a select on the table, not only will the result set contain a +row for each record that satisfied the select condition, but it will also +contain an array for each column, which will hold all the column values. +Here's an example, using the above mentioned table:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result = plane_tbl.select + +puts result[0].name =&gt; P-51 +puts result[0].speed =&gt; 402 + +p result.speed =&gt; [402,354,343]</tt></pre> +</div></div> +<p>Notice how you can reference this "column array" by calling the column name +as a method. KirbyBase returns an array that holds all the values, in this +case, of the speed column. This can be very useful in some cases. For a +good example of this, look in the examples\crosstab_test directory of the +distribution.</p> +</div> +<h2><a id="how-to-update-records"></a>How to update records</h2> +<div class="sectionbody"> +<p>You can update the data in a table either by using the KBTable#update method + by itself, or using it in conjunction with the KBResultSet#set method. +Both methods produce the same result. The main difference is that, while +using the #update method by itself, you can use a Hash, Array, or Struct as +your update criteria, using the #set method in conjunction with the #update +method adds the ability to use a code block as your update criteria. You +will see specific examples of this in "The update method" description below.</p> +<h3><a id="update-method"></a>The update method</h3> +<p>To update a table, you use the update method. You <strong>must</strong> specify a code +block that indicates which records are to be updated. Additionally, you must +specify the fields to be updated and the new values for those fields.</p> +<p>You can update records using a Hash, Struct, an Array, or an instance of a +class you defined. For example, to change the P-51's speed to 405mph and +its range to 1210 miles, you could code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.update(:speed=&gt;405, :range=&gt;1210) { |r| r.name == 'P-51' }</tt></pre> +</div></div> +<p>or:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>UpdateRec = Struct.new(:name, :country, :role, :speed, :range, + :began_service, :still_flying) + +rec = UpdateRec.new +rec.speed = 405 +rec.range = 1210 +plane_tbl.update(rec) { |r| r.name == 'P-51' }</tt></pre> +</div></div> +<h3><a id="set-method"></a>The set method</h3> +<p>You can also update records using a code block, via KBResultSet#set:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.update {|r| r.name == 'P-51'}.set do |r| + r.speed = 405 + r.range = 1210 +end</tt></pre> +</div></div> +<p>You can also update records using a Hash, Struct, or an Array, via +KBResultSet#set:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.update {|r| r.name == 'P-51'}.set(:speed=&gt;405, :range=&gt;1210)</tt></pre> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">When you don't supply a code block to the #select method, +KirbyBase automatically selects all of the records in the table. I felt +that having the #update method work the same way was too dangerous. It +would be too easy for someone to accidentally update all of the records in +a table if they forgot to supply a code block to #update. That is why the +#update method <strong>requires</strong> a code block. To update all of the records in a +table, use the #update_all method.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">The #update method will return an Integer specifying the number of +records that were updated.</td> +</tr></table> +</div> +</div></div> +<h3><a id="update-all-method"></a>The update_all method</h3> +<p>To update all records in a table, you can use KBTable#update_all. This +works just like the update method, except that you don't specify a code +block containing selection criteria.</p> +<p>For example, to add 50 mph to every record's speed field, you would code:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.update_all { |r| r.speed = r.speed + 50 }</tt></pre> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">The #update_all method will return an Integer specifying the number +of records that were updated.</td> +</tr></table> +</div> +</div></div> +</div> +<h2><a id="how-to-delete-records"></a>How to delete records</h2> +<div class="sectionbody"> +<p>Deleting records from a table is similar to performing a #select or an +#update.</p> +<h3><a id="delete-method"></a>The delete method</h3> +<p>To use the #delete method, you <strong>must</strong> supply a code block that identifies +which records should be deleted.</p> +<p>For example, to delete the FW-190's record from the table:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.delete { |r| r.name == 'FW-190' }</tt></pre> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">When you don't supply a code block to the #select method, +KirbyBase automatically selects all of the records in the table. I felt +that having the #delete method work the same way was too dangerous. It +would be too easy for someone to accidentally delete all of the records in +a table if they forgot to supply a code block to #delete. That is why the +#delete method <strong>requires</strong> a code block. To delete all of the records in a +table, use the #clear method.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">The #delete method will return an Integer specifying the total number +of records that were deleted.</td> +</tr></table> +</div> +</div></div> +<h3><a id="clear-method"></a>The clear (alias delete_all) method</h3> +<p>To completely empty a table, use the clear method. For example:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.clear</tt></pre> +</div></div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">By default, KirbyBase will reset the recno counter to 0. So, any +new records inserted after a #clear will be given a :recno starting with 1. +To prevent #clear from resetting the recno counter, pass false to #clear.</td> +</tr></table> +</div> +</div></div> +</div> +<h2><a id="pack-method"></a>The pack method</h2> +<div class="sectionbody"> +<p>When KirbyBase deletes a record, it really just fills the entire line in the +file with spaces. Deleting the entire line and moving each subsequent line +up one would take too much time. Also, when a record is updated, if the size +of the updated record is greater than the size of the old record, KirbyBase +spaces out that entire line in the file, and rewrites the updated record at +the end of the file. Again, this is done so that the entire file doesn't +have to be re-written just because one record got updated.</p> +<p>However, this means that after a lot of deletes and updates, a table can +have lots of blank lines in it. This slows down searches and makes the file +bigger than it has to be. You can use the pack method to remove these blank +lines:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>result = plane_tbl.pack</tt></pre> +</div></div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">You can only call this method when connect_type==:local.</td> +</tr></table> +</div> +<div class="sidebarblock"> +<div class="sidebar-content"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">The #pack method will return an Integer specifiying the number of +blank lines that were removed from the table.</td> +</tr></table> +</div> +</div></div> +</div> +<h2><a id="memo-and-blob-fields"></a>Memo and Blob Fields</h2> +<div class="sectionbody"> +<p>Memo and Blob fields operate a little differently from standard field types. +You specify their creation just like regular field types. Notice how you +can specify a base path for where memo/blob files will be stored.</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.create_table(:plane, :name, :String, :speed, :Integer, :descr, + :Memo) do |d| + d.memo_blob_path = './memos' +end</tt></pre> +</div></div> +<p>However, what you actually store in the Memo field upon an #insert is an +instance of KBMemo. KBMemo has two attributes: :filepath and :contents. +The first holds the path (including filename) to the text file that will +hold the contents of the memo. This path will be relative to whatever +path was specified as the memo_blob_path upon database initialization. Here + is an example of how to do this:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>memo_string = &lt;&lt;END_OF_STRING +The FW-190 was a World War II German fighter. It was used primarily as an +interceptor against Allied strategic bombers. +END_OF_STRING + +memo = KBMemo.new(db, 'FW-190.txt', memo_string) +plane_tbl.insert('FW-190', 'Germany', 399, 499, memo)</tt></pre> +</div></div> +<p>Updates work similarly in that you would need to supply a KBMemo instance +to the #update method for the :Memo field.</p> +<p>Other than this difference, you can use a memo field just like a regular +field. When you do a #select, KirbyBase goes out to the file that holds the +memo data, reads in all the lines, and returns a KBMemo instance. Here is +an example of how you can even query against a memo field:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.select { |r| r.descr.contents =~ /built in Detroit, Michigan/ }</tt></pre> +</div></div> +<p>And KirbyBase would select all records whose memo field contained the phrase + "built in Detroit, Michigan".</p> +<p>Blob fields work similarly, except that instead of doing a #readlines, +KirbyBase opens the file in binary mode and reads in the whole file at once.</p> +</div> +<h2><a id="misc-kirbybase-methods"></a>Miscellaneous KirbyBase methods</h2> +<div class="sectionbody"> +<div class="sidebarblock"> +<a id="drop-table"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KirbyBase#drop_table</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.drop_table(:plane)</tt></pre> +</div></div> +<p>Will delete a table from the database.</p> +<p>Returns true if table was dropped.</p> +</div></div> +<div class="sidebarblock"> +<a id="tables"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KirbyBase#tables</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.tables</tt></pre> +</div></div> +<p>Returns an array of all table names in the database.</p> +</div></div> +<div class="sidebarblock"> +<a id="table-exists"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KirbyBase#table_exists?</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.table_exists?(:plane)</tt></pre> +</div></div> +<p>Returns true if table exists, false otherwise.</p> +</div></div> +<div class="sidebarblock"> +<a id="rename-table"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KirbyBase#rename_table</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>db.rename_table(:plane, :warplanes)</tt></pre> +</div></div> +<p>Rename table to new name.</p> +<p>Returns a handle to the newly-renamed table.</p> +</div></div> +</div> +<h2><a id="misc-kbtable-methods"></a>Miscellaneous KBTable methods</h2> +<div class="sectionbody"> +<div class="sidebarblock"> +<a id="update-by-recno"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#[]=</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl[5] = {:country = 'Tasmania'}</tt></pre> +</div></div> +<p>You can quickly update an individual record by treating the table as a Hash +and the keys as recno's. You can update it using a Hash, Array, or Struct.</p> +<p>Returns Integer specifying number of records updated (should always be 1).</p> +</div></div> +<div class="sidebarblock"> +<a id="get-by-recno"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#[]</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl[5]</tt></pre> +</div></div> +<p>You can quickly select an individual record, by passing its recno to a +table as if it were a hash.</p> +<p>Returns a single record either in the form of a Struct or an instance of +the table's custom record class, if specified.</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl[5, 14, 33]</tt></pre> +</div></div> +<p>You can also use the [] method to get a group of records by passing it more +than one recno.</p> +<p>Returns a KBResultSet with the records having the recno's you passed in.</p> +</div></div> +<div class="sidebarblock"> +<a id="field-names"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#field_names</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.field_names</tt></pre> +</div></div> +<p>Returns an array of the table's field names.</p> +</div></div> +<div class="sidebarblock"> +<a id="field-types"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#field_types</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.field_types</tt></pre> +</div></div> +<p>Returns an array of the table's field types (i.e. :String, :Integer, :Float)</p> +</div></div> +<div class="sidebarblock"> +<a id="total-recs"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#total_recs</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.total_recs</tt></pre> +</div></div> +<p>Returns an Integer specifying the total number of records in the table.</p> +</div></div> +<div class="sidebarblock"> +<a id="import-csv"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#import_csv</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.import_csv(csv_filename)</tt></pre> +</div></div> +<p>This method allows you to import a csv file into the current table. +KirbyBase will attempt to convert the values in the csv file into their +corresponding KirbyBase field types, based upon the field types you +designated when you created the table.</p> +<p>If you have FasterCSV installed KirbyBase will use it instead of CSV from +the standard library.</p> +<p>Returns an Integer specifying the total number of records imported.</p> +</div></div> +<div class="sidebarblock"> +<a id="add-column"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#add_column</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.add_column(:weight, :Integer, :range)</tt></pre> +</div></div> +<p>This method allows you to add a column to an existing table. You must +specify a column name, and a column type. You can optionally specify a +column that you want the new column added after. If this is not specified, +KirbyBase will add the column to the end of the table.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #add_column changes the structure of a table, you can +only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="drop-column"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#drop_column</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.drop_column(:speed)</tt></pre> +</div></div> +<p>This method allows you to remove a column from an existing table. You must +specify the column name to be removed.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">You cannot drop the :recno column.</td> +</tr></table> +</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #drop_column changes the structure of a table, you can +only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="rename-column"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#rename_column</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.rename_column(:speed, :maximum_speed)</tt></pre> +</div></div> +<p>This method allows you to rename a column in an existing table. You must +specify the column to be renamed and a new column name.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">You cannot rename the :recno column.</td> +</tr></table> +</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #rename_column changes the structure of a table, you can +only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="change-column-type"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#change_column_type</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.change_column_type(:weight, :Float)</tt></pre> +</div></div> +<p>This method allows you to change a column's type in an existing table. You +must specify the column name and a new column type.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">You cannot change the type of the :recno column.</td> +</tr></table> +</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #change_column_type changes the structure of a table, you + can only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="change-column-default-value"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#change_column_default_value</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.change_column_default_value(:country, 'United States')</tt></pre> +</div></div> +<p>This method allows you to change a column's default value in an existing +table. You must specify the column name and a default value. If the +default value is equal to nil, this, in effect will make the column not have +a default value any more.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Since the :recno column cannot have a default value, you cannot +change the default value of the :recno column.</td> +</tr></table> +</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #change_column_default_value changes the structure of a +table, you can only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="change-column-required"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#change_column_required</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.change_column_required(:country, true)</tt></pre> +</div></div> +<p>This method allows you to change whether a value for a column is required or +not. You must specify the column name and either true or false for the +required argument.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">You cannot specify whether the :recno column is required or not.</td> +</tr></table> +</div> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #change_column_required changes the structure of a table, + you can only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="add-index"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#add_index</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.add_index(:name, :country)</tt></pre> +</div></div> +<p>This method allows you to add an index to an existing table. This index can + consist of one or more columns. You must specify one or more column names +that you want to make up the index.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #add_index changes the structure of a table, you can +only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +<div class="sidebarblock"> +<a id="drop-index"></a> +<div class="sidebar-content"> +<div class="sidebar-title">KBTable#drop_index</div> +<div class="listingblock"> +<div class="content"> +<pre><tt>plane_tbl.drop_index(:name, :country)</tt></pre> +</div></div> +<p>This method allows you to drop an index from an existing table. You must +specify one or more column names that make up the index that you want to +drop.</p> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/important.png" alt="Important" /> +</td> +<td class="content">Because #drop_index changes the structure of a table, you can +only call this method when connect_type==:local.</td> +</tr></table> +</div> +</div></div> +</div> +<h2><a id="special-characters-in-data"></a>Special characters in data</h2> +<div class="sectionbody"> +<p>Since KirbyBase tables are just plain-text, newline-delimited files with +each field delimited by a <em>|</em>, certain ASCII characters could cause problems +when used as input. For example, entering a newline character (\n on Unix, +\r\n on Windows) as part of a record's data would cause problems later when +KirbyBase attempted to read this record. Likewise, using the <em>|</em> character +in your data would also cause problems as KirbyBase uses this character as a + field delimiter. Finally, it turns out that Python has problems handling +octal code \032 under Windows (possibly because it equates to Ctrl-Z), so +to keep compatibility between the Ruby and Python versions of KirbyBase, +this issue needs to be handled.</p> +<p>To handle the above special characters as data input, KirbyBase checks all +:String and :YAML input data and replaces the special characters with +encodings that are safe. The following table shows how replacements are +done:</p> +<div class="tableblock"> +<table rules="all" +frame="border" +cellspacing="0" cellpadding="4"> +<col width="182" /> +<col width="240" /> +<thead> + <tr> + <th align="left"> + Input Character + </th> + <th align="left"> + KirbyBase Replacement + </th> + </tr> +</thead> +<tbody valign="top"> + <tr> + <td align="left"> + \n + </td> + <td align="left"> + &amp;amp;linefeed; + </td> + </tr> + <tr> + <td align="left"> + \r + </td> + <td align="left"> + &amp;amp;carriage_return; + </td> + </tr> + <tr> + <td align="left"> + \032 + </td> + <td align="left"> + &amp;amp;substitute; + </td> + </tr> + <tr> + <td align="left"> + | + </td> + <td align="left"> + &amp;amp;pipe; + </td> + </tr> + <tr> + <td align="left"> + &amp; + </td> + <td align="left"> + &amp;amp; + </td> + </tr> +</tbody> +</table> +</div> +<p>KirbyBase will translate to and from the special characters as data is +written to and read from a table. It should all be transparent to the user. +The only time you would encounter the replacement words is if you were to +open up the physical table file in a text editor or read it in as input +outside of KirbyBase.</p> +</div> +<h2><a id="table-structure"></a>Table Structure</h2> +<div class="sectionbody"> +<p>Every table in KirbyBase is represented by a physical, newline-delimited +text-file. Here is an example:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>000006|000000|Struct|recno:Integer|name:String|country:String|speed:Integer +1|P-51|USA|403 +2|P-51|USA|365 +3|Sptitfire|England|345 +4|Oscar|Japan|361 +5|ME-109|Germany|366 +6|Zero|Japan|377</tt></pre> +</div></div> +<p>The first line is the header rec. Each field is delimited by a "|". The +first field in the header is the record counter. It is incremented by +KirbyBase to create new record numbers when records are inserted.</p> +<p>The second field in the header is the deleted-records counter. Every time a +line in the file is blanked-out (see <a href="#pack-method">The pack method</a>), this +number is incremented. You can use this field in a maintenance script so +that the table is packed whenever the deleted-records counter reaches, say, +5,000 records.</p> +<p>The third field in the header is the record_class field. If you specified a +class when you created the table, its name will show up here and records +returned from a #select will be instances of this class. The default is +"Struct".</p> +<p>The fourth field in the header is the :recno field. This field is +automatically added to the table when it is created. The field name and +field type are separated by a ":".</p> +<p>The rest of the fields are whatever you specified when you created the +table.</p> +<p>If there is a Z in the first position of the header rec and the rest of the +file is a bunch of random characters, this means that the table is +encrypted.</p> +<p>Each record after the header record is simply a line of text. Newline +characters delimit records.</p> +</div> +<h2><a id="server-notes"></a>Server Notes</h2> +<div class="sectionbody"> +<p>There is a server script included in the distribution called kbserver.rb. +This script uses DRb to turn KirbyBase into a client/server, multi-user +DBMS. This DBMS server handles table locking for you so you don't have to +worry about it.</p> +</div> +<h2><a id="tips-on-improving-performance"></a>Tips on improving performance</h2> +<div class="sectionbody"> +<h3>Beware of Date/DateTime</h3> +<p>Converting a String (the format in which data is stored in a KirbyBase +table) to a Date/DateTime is slow. If you have a large table with a +Date/DateTime field, this can result in long query times.</p> +<p>To get around this, you can specify the field type as a :String, instead of +a :Date/:DateTime. Queries will still work correctly, because Date/DateTime + fields that are in String format sort the same way they would if they were +in native format. Here's an example. The following two expressions will +result in the same result set being returned:</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>date_field &lt;= Date.new(2005, 05, 11)</tt></pre> +</div></div> +<p>and</p> +<div class="listingblock"> +<div class="content"> +<pre><tt>date_field_stored_as_string_field &lt;= "2005-05-11"</tt></pre> +</div></div> +<h3>Create indexes on large tables</h3> +<p>The larger a table becomes, the more sense it makes to create an index on +one or more of its fields. Even though you take a hit when KirbyBase first + initializes because it has to create the index arrays, you make up for it +after that in greatly improved query speeds. I have noticed speed-ups of +as much as 10x on large tables.</p> +<h3>Create indexes on foreign keys</h3> +<p>If you have a Link_many on a table field, you might want to think about +creating an index on the field in the child table that is being linked to. +When performing a one-to-many link, KirbyBase will automatically take +advantage of an index on the link field in the child table.</p> +<p>By the way, the same holds true for Lookups.</p> +<h3>When possible, always search by :recno</h3> +<p>This might be a no-brainer, but, if you have the chance to select by +:recno, use the built-in #select_by_recno_index method (or the #[] method). +This is even faster than selecting on a regularly indexed field, because the + :recno index that KirbyBase creates for each table is actually a Hash, not +an Array like all of the regular indexes. So selects are even faster.</p> +</div> +<h2><a id="single-user-diagram"></a>Single-user memory space diagram</h2> +<div class="sectionbody"> +<div class="imageblock"> +<div class="content"> +<img src="images/single_user.png" alt="images/single_user.png"/> +</div> +<div class="image-title">Figure: Single-user (embedded) mode.</div> +</div> +</div> +<h2><a id="client-server-diagram"></a>Client/Server memory space diagram</h2> +<div class="sectionbody"> +<div class="imageblock"> +<div class="content"> +<img src="images/client_server.png" alt="images/client_server.png"/> +</div> +<div class="image-title">Figure: Client/Server (separate memory spaces) mode.</div> +</div> +</div> +<h2><a id="license"></a>License</h2> +<div class="sectionbody"> +<p>KirbyBase is distributed under the same license terms as Ruby.</p> +</div> +<h2><a id="credits"></a>Credits</h2> +<div class="sectionbody"> +<div class="admonitionblock"> +<table><tr> +<td class="icon"> +<img src="./images/note.png" alt="Note" /> +</td> +<td class="content">This manual was produced using the awesome text document formatter, +<a href="http://www.methods.co.nz/asciidoc/">AsciiDoc</a>.</td> +</tr></table> +</div> +</div> +<div id="footer"> +<div id="footer-text"> +Last updated 26-Jun-2006 14:36:38 EDT +</div> +</div> +</body> +</html>