|
Integrated Database Details |
current record, the
special variable _extracted is set to zero, otherwise it is the number of records
that the operation produced.
sort by, sort descending by,
or sort nocase by clauses.FOR EACH statement begins execution.When a table is opened for input, records are automatically unlocked as they become current.
When a table is opened for update or output, records are automatically locked whenever a field value is modified.
In cases where a field is being incremented or otherwise read and
written in the same operation, it is recommended to explicitly lock
the table first using lock table. This ensures correct
behavior in concurrent environments.
Whenever a new record is made current, any previously locked records are virtually written to storage and unlocked.
Modified records are physically written to storage using delayed writes, allowing updates to be batched efficiently.
To explicitly unlock a table, use:
To unlock all tables, use:
To flush all virtual writes for a table and commit them to storage, use:
To commit all writes for all tables, use:
Sheerpower provides four main methods for key lookups in tables:
After any key lookup or extract operation, _extracted
contains the number of matching records found.
_extracted will be zero if no record
was found; otherwise it will be one.
set table is intended for lookups that expect at most
one matching record.
for each block
the special variable _pointer is the current iteration counter.
The exclude statement is used to exclude records from the
extract block that match the logical expression nn.
Example:
This example excludes employees who are under 18 years old and those who have an inactive status from the extract block.
The include statement is used to include only
those records that match the logical expression nn.
Example:
This example includes only sales records from the North region with an amount greater than $1000.
The exit extract statement is used to exit the extract block and with the collected records up to that point.
Example:
This example exits the extract block if an order date is before January 1, 2023, and processes the collected records.
The cancel extract statement is used to cancel the entire extract block without collecting any records.
Example:
This example cancels the extract block if any item has a quantity of 0, resulting in no records being processed.
The sort by xxx statement is used to sort the records in ascending order based on the expression xxx.
Example:
This example sorts the customer records by their last names in ascending order.
The sort descending by xxx statement is used to sort the records in descending order based on the expression xxx.
Example:
This example sorts the product records by their prices in descending order.
The sort key can be any expression, evaluated once per record — useful when the interesting order lives in a computed value no single field holds. Here order lines are ranked by their extended value, price times quantity:
The most valuable lines come first — which need not be the highest-priced products or the biggest quantities, only the largest result of multiplying the two. The expression decides the order and nothing more; no field in the table changes.
The sort nocase by xxx statement sorts the records in
ascending order based on the expression xxx, ignoring the
difference between uppercase and lowercase letters. Without
nocase, all uppercase letters sort ahead of all
lowercase letters ('Zebra' would come before 'apple').
The keyword order is fixed: sort [descending] [nocase] by xxx
— nocase goes right before by.
Example:
This example sorts the customer records by city so that 'boston', 'Boston', and 'BOSTON' group together. Records whose keys are equal when case is ignored keep their original order.
Case sensitivity is chosen per sort key, so a case-blind major sort can be combined with a case-sensitive minor sort in the same extract:
The sort by statement is executed once for each record, so
it can live inside an if and supply a different
expression for different records. A classic use is a fallback
key: sort clients by their home zipcode, but for clients who have
no home zipcode on file, use their billing zipcode instead:
Every record contributes exactly one key value — whichever zipcode its branch chose — and the collection is sorted as a single unified zipcode order.
The one rule: every branch must agree on each sort key's
data type, direction, and case
sensitivity, and supply the same number of sort keys.
Sorting by a string in one branch and a number in another raises a
catchable INCONSORT exception (−4112); changing
the number of sort keys between records raises BADSORT
(−4111).
Statement order inside extract is program order: the
statements run top to bottom, once for each record. When an
include fails (or an exclude matches),
execution of the block stops for that record — statements below
it, including sort by, never run for the rejected record,
and it contributes no sort key — and therefore never counts
toward groupmin or groupmax. So placing
include/exclude before sort
by skips the key-building work for records that will be
discarded. Filters may appear anywhere in the block — before,
between, or after the sort keys — and the result is the same;
filtering first simply does less work.
The groupmin n attribute is applied after the sort completes: it keeps only groups — records that are equal on the complete set of sort keys — containing at least n records. Its most common use is the classic data-cleanup question: which values appear more than once?
Here the client table is checked for duplicate phone numbers.
Sorting by phone brings equal numbers together;
groupmin 2 then keeps every record of every
duplicated number, with the one-of-a-kind numbers gone:
Each duplicated number arrives with all of its records, grouped together in sorted order — exactly what is needed to review or merge the duplicates. The table itself is untouched.
groupmax n asks the opposite question of the same
sort: keep only the groups with at most n records.
groupmax 1 on the same table returns the phone numbers
that occur exactly once — the complement of the duplicate
report:
The two compose: extract table clients: groupmin 2,
groupmax 2 keeps exactly the pairs. A natural real-world use
of groupmax 1 is reconciliation:
extract two sources into one collection (or load them into one
cluster) with a field marking each record's source, sort by the
matching key, and groupmax 1 hands you every key that
appears in only one source — the orphans — while
groupmin 2 on the same sort gives the matched keys. One
sort, both reports.
groupmin / groupmax let you select groups
by how many records they have without counting anything
yourself. groupmin finds the crowded groups,
groupmax the sparse ones, and together they find the
exactly-right-sized ones — all in the single pass the sort was
already doing.
Points worth knowing:
groupmin is written after the colon and
comma-chains with the other extract options
(extract table clients: cachesize 100, groupmin 2).sort by clients(last_name) plus
sort by clients(first_name) finds full-name
duplicates. Under sort nocase by, values
differing only in case count as one group.groupmin or groupmax is used, the
sort runs immediately at end extract (instead of
being delayed), so
_extracted is the filtered record count right
away.groupmin 1 keeps everything; a value below 1
raises a catchable GROUPBAD exception
(−4114), and groupmin without any
sort by is a compile error
(GROUPNOSORT, −4113) — groups
are defined by the sort keys.groupmax n is the mirror image:
it keeps only groups with at most n records.
groupmax 1 finds the one-of-a-kind values, and
the two compose — extract table clients:
groupmin 2, groupmax 2 keeps exactly the pairs. Same
rules (sort required, values below 1 raise
GROUPBAD), plus a groupmax below the
groupmin raises GROUPBAD too, since
no group could ever survive it.Because include and sort by are
ordinary executable statements, they can work with values your own
code computes as each record goes by. That turns an extract block
into a scoring model: award points for each desirable
trait, keep the records that score high enough, and rank the
survivors best-first. Here house listings are scored on bedrooms
and lot position:
Each record is scored fresh (score = 0 runs once
per record), include keeps only records reaching the
threshold, and sort descending by score presents the
best matches first. A listing that sweeps every trait scores 135
and leads the list — but a one-bedroom with a two-car
garage, priced within budget, and 1,800+ square feet qualifies at
65 points on those traits alone. That is exactly the near-miss a
rigid “must have 2+ bedrooms AND ...” filter would
discard, and it is what makes the selection fuzzy: no
single condition is required, the traits just have to add up.
Notice that the model reads budget — an
ordinary variable set before the extract — so the same
scoring code serves any buyer. The weights are ordinary numbers
your code controls: refining the model is editing a few
if lines, not restructuring a query.
The extract table block creates a collection of table records. When the
EXTRACT BLOCK is exited, the special variable _extracted contains the
number of records in the most recent extract. Typically after an extract
block is a FOR EACH block that iterates through the collection.
If the EXTRACT BLOCK contained any SORT statements,
the sorting is delayed until the FOR EACH block is executed.
This enables dramatic performance improvements if later using the reextract or
append options.
Code Explanation:
1. Open Tables:
These lines open two tables named client and detail from the specified file paths (@..\safe\client and @..\safe\detail). The tables are made available for further operations like extracting records, querying, and sorting.
2. Extract All Records:
This block extracts all records from the client table. The _extracted variable holds the number of records extracted, which is then printed to inform the user how many records were found.
3. Set ID Based on Input:
This conditional block checks if the string a$ has a length greater than 0 (i.e., it is not empty). If a$ is not empty, my_id$ is set to the value of a$; otherwise, my_id$ is set to the default ID '80522'.
4. Lookup Record by ID:
This block sets the client table to look for a record with an id matching my_id$. The _extracted variable will hold the number of matching records (either 0 or 1).
5. Print Client Information:
If no record was found (_extracted = 0), the program prints "Unknown ID". Otherwise, it prints the client's last and first names.
6. Extract Clients from California (Excluding Area Code 619):
This block extracts records from the client table where the state is 'CA', excluding those whose phone number starts with '619'. The results are sorted in ascending order by the last name.
7. Print Extracted Clients:
This loop iterates over each extracted client and prints their ID, first name, last name, and phone number.
8. Extract Clients Excluding Area Code 619:
This block extracts records from the client table, excluding those whose phone number starts with '619'. No sorting is specified.
9. Print Extracted Clients:
This loop iterates over each extracted client and prints their first name, last name, and phone number.
10. Sort Clients by State and Last Name:
This block extracts and sorts clients first by state (major sort) and then by last name (minor sort).
11. Print Sorted Clients:
This loop iterates over each sorted client and prints their last name, first name, and state.
12. Extract Clients with Last Name Starting with 'R':
This block extracts records from the client table where the last name starts with the letter 'R'.
13. Print Clients with Last Name Starting with 'R':
This loop iterates over each client whose last name starts with 'R' and prints their first and last names.
14. Re-extract Clients from California:
This block extracts records from the client table where the state is 'CA'. The records are held for further re-extraction.
15. Re-extract and Sort Clients in Area Code 619:
This block further filters the already extracted records, keeping only those whose phone number starts with '619', and sorts them by last name.
16. Print Sorted California Clients in Area Code 619:
This loop iterates over the re-extracted records and prints the first name, last name, and phone number of clients in California with the '619' area code.
17. Extract Details with Specific Line IDs (First Range):
This block clears previous extractions (extracted 0) and extracts records from the detail table with lineid between '10301001' and '10302000'. The results are sorted by prodnbr and invnbr.
18. Extract Details with Specific Line IDs (Second Range):
This block appends to the previous extraction, extracting records with lineid between '10311001' and '10312000'. The results are sorted similarly by prodnbr and invnbr.
19. Print Header for Details:
Prints a header for the detail records, aligning columns for Prod, Line ID, and Quantity using tab() for spacing.
20. Print Extracted Details:
This loop iterates over each extracted detail record and prints the product number, line ID, and quantity, aligned under the header.
In Sheerpower, after you've finished working with a table, it's important to properly close it to free up system resources and ensure that all data operations are completed correctly. The correct syntax for closing a table is:
First, you open the table using the open table
command.
You can perform various operations like extracting, filtering, sorting, and iterating over records.
Once you've completed your operations, use the close
table statement to close the table.
Closing tables ensures that:
Using close table tablename is the recommended
practice to maintain the integrity and efficiency of your Sheerpower
applications.
Sheerpower's integrated database delivers table scanning speeds exceeding 1.5 million records per second with 1000 byte records on modern consumer PCs. This performance is achieved through:
extract then for each)Sheerpower provides record-level concurrency, enabling multiple users to safely access and modify data simultaneously without blocking entire tables. This means:
On a typical modern PC, this operation completes in well under one second, even with concurrent users accessing the same table.
Understanding the physical layout of the ARS database engine helps explain why Sheerpower's database behaves the way it does—why sequential scans are so fast, why multiple keys have no performance hierarchy, and why there is no concept of a primary key.
ARS stores records in a linked list of large data buckets. Each bucket holds many records packed together, and the buckets are chained in sequence. This is the physical home of all record data—field values live here, and only here.
Keys are stored separately, in key buckets. Each key bucket holds key values together with pointers back into the data buckets. When you look up a record by key, ARS navigates the key bucket to find the pointer, then follows that pointer directly to the data record.
This separation—data in one structure, keys in another—is the foundation of every performance and concurrency property the engine provides.
Why this matters: In many traditional database engines, records are physically organized around a single "clustering key." Changing that key means moving the record. In ARS, the data record never moves—only the key bucket entry changes. This makes all key updates equally inexpensive, regardless of which key is being changed.
Solution: By decoupling key storage from data storage, ARS treats every key as an equal citizen. There is no primary key that owns the physical layout, and no secondary keys that pay a performance penalty for that reason.
Efficiency: Adding a new key to a table creates a new key record pointing into an existing data bucket. The data records themselves are untouched. Key lookups are fast regardless of how many keys the table carries.
Takeaway: All keys in ARS are changeable, all are equal, and none owns the data. The uniqueness of a record identifier is the application's responsibility—not a database constraint.
ARS has no concept of a primary key. Every key defined on a table is simply a key—a navigational path into the data buckets. Any key value can be changed at any time without reorganizing the underlying data.
When a Sheerpower application needs a stable, unique record identifier,
use _gid$—a 30-byte globally unique identifier
(YYYYMMDD + UUID) generated by the Sheerpower runtime.
Because _gid$ includes a universally unique identifier (UUID),
two instances of the same handler running simultaneously will never
produce the same value, even when running on different systems.
This eliminates the need for counters, sequence generators,
or inter-process coordination.
Because all data records live in a linked list of large, packed data buckets, a full table scan is a straight sequential walk through those buckets—no index navigation, no random access, no page splits to work around. Modern CPUs and memory controllers are highly optimized for exactly this access pattern. This is why Sheerpower delivers over 1.5 million record scans per second on consumer hardware.
When you write an extract table block without a key clause, ARS
walks the data bucket chain from start to finish. The performance you see is
a direct consequence of the physical layout—not a tuning achievement.
Record-level locking in ARS is straightforward because of the physical separation between key buckets and data buckets. When a record is locked, the lock is on the data record—not on any key entry, not on a page, not on the table. Other processes can still navigate key buckets freely, and can read or write any other data record in the same table without interference.
Changing a key value on a locked record updates only the key bucket entry. The data record itself stays in place in its data bucket. This means a key change and a field value change both touch only the minimum necessary structures—and lock only what they actually modify.
Why this matters: Traditional page-level or table-level locking blocks all readers and writers on that page or table while any single operation is in progress. Under load, this becomes a bottleneck that scales poorly.
Solution: ARS locks individual data records. The lock scope is exactly as wide as the operation requires—one record—and no wider. Readers and writers on other records in the same table proceed without waiting.
Efficiency: The unlock all: commit pattern
at the top of a transaction loop releases all locks from the previous
request and forces dirty records to be written to storage.
As a result, no locks are held during idle time. Waiting is bounded by the duration of actual computation—not by network or client delays.
Takeaway: Fine-grained record-level locking combined with disciplined commit boundaries means multiple handler instances can read and write the same table simultaneously with little waiting.
Because the ARS database engine is integrated directly into the Sheerpower runtime, deadlock detection has access to information that an external database engine cannot see—the exact source lines executing in each process at the moment the deadlock occurs.
When a deadlock is detected, the Sheerpower diagnostic utility
arslockmon reports not just the process IDs involved, but the
precise source files and line numbers where each process is waiting.
An investigation that might take hours of log correlation in a traditional setup becomes immediate.
Many database systems accept queries as strings of text that are parsed and executed at runtime. A malicious user who can control part of that string can inject additional commands—altering the query's intent, bypassing access controls, or destroying data.
Sheerpower's database access works differently at a structural level. There is no query language. There is no string that gets parsed into a command. Key values, field values, and filter expressions are passed directly to the ARS engine as typed data—they are never interpreted as code.
set table customers, field customer_id: key id$
Whatever is in id$ is treated as data. It cannot alter execution.
There is no parser to subvert.
Why this matters: SQL injection is one of the most persistent vulnerabilities in web applications.
Solution: ARS separates data from operations at the architectural level.
Efficiency: No sanitization, no parameterization, no developer burden.
Takeaway: The vulnerability class does not exist in this architecture.
The 1.5 million records per second figure is a direct result of the physical storage layout.
Sequential scans are linear, cache-friendly, and efficient. Key lookups are shallow and consistent. Shared memory caching reduces disk access. Writes are batched at commit boundaries.
Why this matters: Traditional database performance requires tuning and monitoring.
Solution: ARS performance follows directly from its design.
Efficiency: Multiple handler instances scale naturally.
Takeaway: Development performance matches production.
open table allows access to persistent database files
with built-in concurrency and locking support.extract ... end extract to build a filtered and/or
sorted collection of records from a table.for each iterates through the extracted records;
records are current during iteration.set table or extract table ... key._extracted reflects how many records matched the
last extract or key lookup (0 means no match).for each block _pointer is the current iteration counter.include and exclude in
extract blocks to precisely define which records to process.sort by and sort descending by sort
extracted records by one or more fields.cancel extract aborts the current extract block;
exit extract exits early but keeps collected records.reextract applies new filters/sorts to an existing
extracted record set without re-accessing disk.close table tablename
after use to free system resources and ensure data integrity.print table(field) syntax to access individual
fields from a current record.|
Hide Description
|
|
|
Enter or modify the code below, and then click on RUN |
|
Looking for the full power of Sheerpower?
Check out the Sheerpower website. Free to download. Free to use. |