-
Couldn't load subscription status.
- Fork 0
Language Reference Collections
In FBASIC, collections are fundamental data structures used to manage lists of values and result sets from data sources. While traditional single variables hold one value, collections allow a program to store and iterate over multiple related items efficiently. The two primary collection types referenced in the language are Static (or SDATA) and Dynamic (Fetchable).
FBASIC is an extensible computer programming language whose core interpreter provides fundamental structures common to all modern languages, facilitating the easy, statement-by-statement translation to other programming environments. Within FBASIC, data management is distinguished by two primary collection types: static collections, which are typically one-dimensional arrays of data stored directly and contiguously in memory for fast access, and fetchable collections, which are designed for sequential, row-by-row access and do not support direct, indexed lookup of individual rows.
| Characteristic | Static | Dynamic |
|---|---|---|
| In memory | Yes | No |
| Access row by index | Yes | No |
| Access row by fetch | Yes | Yes |
| Need reset | Yes | Yes |
| Foreach loop | Yes | Yes |
| Known number of items | Yes | No |
| Multi field support | No | Yes |
| Dynamic field naming | No | Yes |
| Need data source | No | Yes |
| Data from the program (via SDATA) | Yes | No |
| Statement SSET | Yes | No |
An SDATA collection is a dynamic, in-memory structure primarily designed to store and manage lists of primitive values (like strings or numbers) generated or manipulated within the FBASIC program's execution flow. It serves a similar role to an array or list in other languages, allowing for flexible data aggregation.
The SDATA collection is created and populated using the SDATA statement.
| Statement / Element | Purpose | Example |
|---|---|---|
| SDATA collection value | Appends a single value to the end of the collection. | sdata names "John" |
| SCLEAR collection | Clear the collections and empty all the contents. The collection is valid but empty of items. prior to clear the statements performs a RESET | SCLEAR names |
| SSET collection index identifier | Change the item that the index points to the value or variable | SSET names 1 "Jim" |
The following example demonstrates initializing an SDATA collection and populating it within a loop:
inputLoop:
input nameInput
If nameInput = "." Then
GoTo endInput
EndIf
REM Store the name into the SDATA collection
sdata names [nameInput]
GoTo inputLoop
endInput:The standard way to access all items in an SDATA collection is using the FOREACH...ENDFOREACH loop structure.
| Statement / Element | Purpose | Example |
|---|---|---|
| RESET collection | Reset a collection and move the underlyning cursor to the first item | RESET names |
| FOREACH collection | Move the underlyning cursor to the next row of a collection. Do not use FETCH inside FOREACH loop | FOREACH names ..... ENDFOREACH names |
| ENDFOREACH collection | Trigger to fetch the next row from the collection and reloops | ENDFOREACH names |
| EOD(collection) | Functions that inititalized and affected by the CURSOR, FETCH, REST, SCLEAR statements and is true when End Of Data (no more data) occurs | if EOD("names") THEN ... ENDIF |
Attention:
- FOREACH...ENDFOREACH loop implicates an automated FETCH on every interation. Do not use FETCH inside such loops.
Example of Iteration:
Foreach customers
print [names.FirstName]
EndForeach customersFBASIC provides dedicated statements and functions for advanced management of SDATA collections, granting array-like control.
| Statement / Element | Purpose | Example |
|---|---|---|
| SSET collection index value | Sets/updates the value of an item at a specific, 1-based index in the SDATA collection. | SSET names 1 "Jim" |
| ubound("collection") | Function returns the total number of elements (upper bound) currently stored in the SDATA collection. | print ubound("names") |
| SCI("collection", index) | Function returns the value of the item at the specified 1-based index in the SDATA collection. | print SCI("names",1) |
| [collection.Item] | An identifier with a special syntax used to reference the current fetched item's value. Need FETCH or FOREACH loop before use it. | [names.Item] |
Special attention:
- Function names for collections use capital (uppercase) letters (e.g., SCI), which contradicts the convention for most other functions in FBASIC. Remember that statements are case-insensitive, but functions are case-sensitive. Exception is the ubound() that it is buildin basic function common for arrays, collections etc.
- The collection name passed to these functions is treated as a string value or variable, not an identifier. Therefore, a literal collection name must be enclosed in quotes.
- In FBASIC most of the of statements have the arguments as space separted, and functions have them as comma separated. This happend to avoid parsing waste time
Example using SSET and ubound():
sdata scores 95 ' stores 95 at index 1
sdata scores 88 ' stores 88 at index 2
let total = ubound(scores)
print "Total items: " + total ' Prints 2
SSET scores 2 90 ' Change value at index 2 from 88 to 90
let newScore = SCI(scores, 2)
print "New score at index 2: " + newScore ' Prints 90The core of FBASIC interpreter supports the functionallity of Dynamic and Fetchable Data, but does not offer any way to use them because does not have builtin functionality to access any kind of such data. To gain access to your data via a dynamic collection, you need to implement a Data Provider and a Library that will give programming statements and functions to the FBASIC interpreter.
With the package as addons are avaiable the following FBASIC Libraries:
- FBasicSQLDataAdapter, where provide access to fetchable SQL Statements, is a special type of collection that acts as a wrapper for external data sources, most of the cases SQL results. It is designed for efficient, record-by-record retrieval of data without loading the entire dataset into memory at once.
Each of the libraries offering Dynamic Data Collection functionallity, implements its own statements to declare the fetchable data. The buildin FBasicSQLDataAdapter is using the statement CURSOR where it is included into current documentation.
| Statement / Element | Purpose | Example |
|---|---|---|
| CURSOR collection, sql_statment | Declare a SQL cursor for a data retrival command (eg SELECT) | cursor DATA1, "select top 20 id,name from friends order by name" |
Attention: In contradiction with most of the FBASIC's statements, CURSOR syntax needs comma (,) after the collection name.
Example:
Cursor c1, "select top 20 id,name where type='S' from sysobjects order by name"| Statement / Element | Purpose | Example |
|---|---|---|
| RESET collection | Reset a collection and move the underlyning cursor to the first item | RESET names |
| FOREACH collection | Move the underlyning cursor to the next row of a collection. Do not use FETCH inside FOREACH loop | FOREACH names ..... ENDFOREACH names |
| ENDFOREACH collection | Trigger to fetch the next row from the collection and reloops | ENDFOREACH names |
| EOD(collection) | Functions that inititalized and affected by the CURSOR, FETCH, REST, SCLEAR statements and is true when End Of Data (no more data) occurs | if EOD("names") THEN ... ENDIF |
| sql(v) | Surrounds the input value with quotes if the input variable is string | sql(1) or sql(var1) or sql("john") |
| [collection.field_name] | Field reference getting the value of a field from the currently fetched record. | print "[c1.id], [c1.name]" |
Note: Functions and statements designed for static collections (e.g., _SSET_, _ubound(), _SCI(), [collection.Item]) do not apply to dynamic collections. Attempting to use them on dynamic collections may result in unpredictable behavior or errors.
This is the most common and robust method for processing all records in the result set.
Foreach DATA1
sdata names \[DATA1.name\]
EndForeach DATA1This method offers fine-grained control over reading records, often used with conditional branching using EOD().
dataloop1:
fetch DATA1
If EOD("DATA1") Then
GoTo exitloop
EndIf
let msg= "ID: " \+ sql(\[DATA1.id\]) \+ ", Name: " \+ sql(\[DATA1.name\])
print msg
GoTo dataloop1
exitloop:Using the statement SDATA the developer can define a static collection of data. For further information see the following example:
REM SDATA Test
REM
sdata Months "Jan","Feb","Mar"
sdata Months "Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
sdata DayNo 1,2,3,4,5,6,7
let ln=1
Foreach Months
let msg=str(ln)+". "+[Months.Item]
print msg
let ln=ln+1
EndForeach Months
assert ln = 13
print "Second list:"
reset Months
Foreach Months
print [Months.Item]
EndForeach Months
print "Day Numbers:"
Foreach DayNo
print [DayNo.Item]
EndForeach DayNo REM Data count loop
Cursor DATA1, "select top 2 name from sysobjects order by name desc"
Cursor DATA2, "select top 5 id,name from sysobjects order by name"
let row=0
let tms=0
REM loop
ForEach DATA1
ForEach DATA2
let row=row+1
let msg= str(row)+": "+sql([DATA1.name])+", "+[DATA2.name]
print msg
EndForEach DATA2
reset DATA2 'Reset it is necessary only in the case that the flow will pass again from the FOREACH statement.
EndForEach DATA1SQL Data provider is designed to provide data collections based on a SQL statement and handle it as a read only cursor. To active it add the library:FBasicSQLDataAdapter. Adding the following extensions to the language:
REM Data print and count loop, two times
REM
Cursor DATA1, "select top 20 id,name from sysobjects order by name"
let row=0
let tms=0
REM loop
dataloop1:
fetch DATA1
If EOD("DATA1") Then
GoTo exitloop
End If
let row=row+1
let msg= str(row)+": "+sql([DATA1.id])+", "+sql([DATA1.name])
print msg
GoTo dataloop1
exitloop:
REM end loop
print "***EOD****"
let tms=tms+1
If (tms = 1) Then
reset DATA1
GoTo dataloop1
EndIfLibrary FBasicSQLDataAdapter does not have any mechanism to specify the connection to the database, but is using the Request For Object Handler to request an initialized database connection object. So, the delgation for the requestForObjectHandler of the interpreter has to be specified before program execution.
Durring the program execution, when the data adapter needs the database connection object, use a request with the following parameters:
| Context | Group | Name | |
|---|---|---|---|
| Values: | SQL | CONNECTION | cursor_name |
| Example: | SQL | CONNECTION | DATA1 |
Example of Interpreter Initialization
this.env = new();
env.DefaultEnvironment();
...
env.requestForObjectHandler = (context, group, name) =>
{
if ($"{context}.{group}.{name}" == "SQL.CONNECTION.DATA1")
{
string cs = "<replace with your CS hear>";
var connection = new OdbcConnection(cs);
return connection;
}
if ($"{context}.{group}.{name}" == "IN.TEST.NAME") return "THIS IS AN IN TEST!";
return null;
};
env.AddLibrary(new FBasicSQLDataAdapter());
....