Table of Contents

1 Introduction

1.1 Command Line Interpreter

1.2 Hello World

1.3 Main Wafl Concepts

1.4 Introduction to the Types

2 Program Structure

2.1 Program Is an Expression

2.2 Comments

2.3 Tuples

2.4 Local Definitions

2.5 Function Definition

2.6 Named Expression Definition

2.7 No Variables

2.8 The Order of the Definitions

2.9 Conditional Expression if

2.10 Conditional Expression switch

2.11 Recursion

2.12 Libraries

3 Programming With Functions

3.1 Strict Type Checking

3.2 Automatic Type Inference

3.3 Polymorphism

3.4 Higher Order Functions

3.5 Partial Application

3.6 Lambda Functions

3.7 Lambda Closures

3.8 Operators as Functions

3.9 Dot Operator

3.10 Explicit Computation State

3.11 Cached Functions

4 Primitive Types

4.1 Literals

4.2 Operators

4.3 Conversion Functions

4.4 Integer Functions

4.5 Float Functions

4.6 String Functions

5 List Type

5.1 List Literals

5.2 Basic List Functions

5.3 Basic List Processing

5.4 Advanced List Processing

5.5 More List Functions

5.6 Functions map and mapIdx

5.7 Functions zip and cross

5.8 Functions foldr, foldl and fold

5.9 Selection Functions

5.10 Functions for Finding Elements

5.11 Functions forall and exists

5.12 Functions count and countRep

5.13 Functions sort and sortBy

5.14 Functions in and contains

5.15 Lazy Lists

6 Structured Types

6.1 Array Type

6.2 Map Type

6.3 Tuple Type

6.4 Record Type

7 Databases

7.1 Database Connection

7.2 Queries

7.3 Transactions

8 Elements of Wafl Library

8.1 Program Control

8.2 File Reading

8.3 File Writing

8.4 File Operations

8.5 Directory Operations

8.6 Regex Functions

8.7 Command Line

8.8 Web and HTTP Functions

8.9 Wafl to JSON

8.10 Wafl Program Evaluation

9 Parallelization

9.1 Wafl and Parallelization

9.2 Parallel Functions

10 Wafl Library Reference

10.1 Core Library

10.2 Binary Libraries

10.3 Wafl Libraries

11 Command Line Reference

11.1 Command Line Options

11.2 Configuration Files

11.3 ANSI Control Codes

12 Advanced

12.1 JIT

12.2 Wafl Binary Libraries

13 More Content…

13.1 Soon…

 

 

Last update: 16.09.2025.

Wafl

Wafl

Tutorial / 7 - Databases

Open printable version

7 Databases

7.1 Database Connection

Each database use occurs within a single database session, which uses a specific database connection. Simple database programs use a single database session and a single database connection.

The more complex case of programs having multiple database connections is not covered by this tutorial, yet.

In Wafl, programs implicitly connect to a database, based on defined connection parameters. The programmer does not explicitly establish or break the connection between the program and the database.

In command-line programs, connection parameters can be defined in two ways: using command-line arguments and using configuration files. In both cases, to define a connection, it is necessary to specify the driver, database name, username, and password.

7.1.1 Command Line Database Connection Parameters

Driver is selected using the -dbdriver:<driver-name> option, where <driver-name> can be Odbc, Db2v11, Sqlite and None.

Database is selected using the -db:<db-alias> option, where <db-alias> is the name of the database that is visible to the selected driver. For Sqlib, alias is the filename of the database file. * Both the driver and the database can be specified with a single option -db:<driver-name>:<db-alias>.

Username is specified using the -user:<username> option. In some cases, and depending on the database driver and operating system, the username is not required (for Sqlib) or can be detected from the current terminal session (Db2).

Password is specified using the -pwd:<password> option. Both username and password can be specified by a single option -user:<username>:<password>.

Specifying Password

In command-line programs, it is recommended not to specify a password in the command line, especially if the commands are specified in batch files.

If the program is running in console mode and the password is not entered or is incorrect, the program will prompt the user to enter the correct password three times.

7.1.2 Configuration File Database Parameters

Some of the database connection parameters can be set using configuration files. These parameters are located in the [database] section:

  • The default-driver parameter defines the default database driver that is used if no other driver is explicitly specified.
  • The default-db parameter defines a database name that is used if no other database is explicitly specified.
  • The default-query-type parameter defines whether queries are typed or untyped by default. It can be typed or untyped.

For security reasons, it is not possible to specify a username or a password in configuration files.

Here is an example of the [database] section in a configuration file:

[database]
default-db = Test
default-driver = Db2v11
default-query-type = untyped

For more detail please see the Configuration Files section.

7.2 Queries

7.2.1 Query Functions

The usual way to write database queries in Wafl is to define query functions. Query functions have a custom syntax, but they evaluate in the same way as any other Wafl function.

A query function definition begins with the keyword sequence [typed | untyped] [sql] query, followed by the query enclosed in curly braces. The function arguments can be used in the query using the usual host variable syntax supported by many database tools.

The keywords typed and untyped specify the function return type. If neither is specified, the default behavior depends on the implementation version and the configuration files. It is strongly recommended to always specify one of these keywords.

In both cases, the result of the query is a list of rows, but the rows can be represented by records (typed rows) or by maps of string values (untyped rows).

The sql keyword specifies that the query is written in SQL. No other query languages are supported in this version of Wafl.

For example, the following query function returns a list of books with a given title:

qBooks( title ) = untyped sql query {
    select * from books
    where book_title = :title
};

Untyped Queries

The untyped query function returns a list of string maps: List[Map[String][String]]. Each value returned by the database is converted to its string representation and returned as a string. The processing of rows is the same as the processing of the lists and string maps map types.

For example, the following code creates the report on books whose titles contain the given string, in CSV format. Each row is converted to a line of text, using a lambda function \r:..., and the rows are glued together using \n as a separator:

booksReport( titlePart ) = 
    'Title,Author,Pages\n'
    + qBooks( '%' + titlePart + '%' )
        .map(\r: r['title']) + ',' + r['author'] + ',' + r['pages'])
        .strJoin('\n');

qBooks( titleTpl ) = untyped sql query {
    select title, author, pages
    from books
    where book_title like :titleTpl
};

The type of the query function qBooks is: (String -> List[Map[String][String]]).

The column names are case-sensitive, but the row column names can have different case that the column names specified in the query. That depends on the database used.

Untyped queries all have the same result type. If we need to write some code that handles the results of many different queries in a general way, then it can be better to use untyped queries.

Typed Queries

The typed query function returns a list of records, where the records elements correspond to the columns by both name and type. The type of the query result is checked during the type-checking of the program.

For example, the code equivalent to the previous example, but with typed queries, can be written as follows:

booksReport( title ) = 
    'Title,Author\n'
    + qBooks( title )
        .map(\r: r$title + ',' + r$author + r$pages$)
        .strJoin('\n');

qBooks( title ) = typed sql query {
    select * from books
    where book_title = :title
};

The query function type is: (String -> List[Record[author: String, pages: Integer, title:String]].

The elements of record types are usually listed in lexicographical order, but this is not important. They are always accessed by name, never by their position.

Untyped queries are always strongly type-checked in the same way as the other Wafl functions. Thus, they are better to be used when we need to be sure that the data that comes from the database is of the expected and appropriate type.

Query Arguments

Query arguments are analyzed and type-checked in the same way as the column types. If the database provides the data on the query parameters, these data is used to determine the argument types. For example, for most of the relational databases and the usual ODBC interface, the following query will have correctly determined arguments:

qBooks( title, pages, author ) = untyped sql query {
    select *
    from books
    where title = :t
       or pages = :p
       or author = :a
};

The type of this function should be determined as: (String * Integer * String -> List[Map[String][String]]).

Arguments are handled the same way for typed and untyped queries. However, some databases and database drivers may only accept string arguments, regardless of where the arguments are used in the query.

Dynamic Queries

If a query text is not known in advance when the program is developed, then database can be queried using queries defined as strings. In such cases the queries cannot include arguments - the arguments values should be embedded in the query text.

The library function dynSqlQuery gets a query text and returns a record that contains the query execution status and the resulting rows. Its type is: (String -> Record[errCode:Int, errText:String, ok:Bool, result:List[Map[String][String]]]).

  • ok - a simple logical result, true if everything is correct;
  • errCode - the standard SQLCODE integer value;
  • errText - the textual representation of the error;
  • result - result, as a list of string maps.

When arguments are manually embedded in the queries, this can present a complex security issue!

This kind of queries is called dynamic because the query text is usually dynamically generated before the execution. This has nothing to do with the terms static and dynamic queries the context of relational databases. In this sense, all queries in Wafl are dynamic, because they are all prepared and executed when the program starts.

Lazy Semantics

All query functions use lazy reading - the result of the query is a so-called lazy list. No rows are retrieved from the database before attempting to read the corresponding element of the resulting list. Rows are read as much as are actually needed.

Sometimes it takes very little to mess things up. For example, it’s enough to try to calculate the length of a list that represents the result of a query, and lazy semantics are lost - the only way to accurately calculate the number of rows in the response is to retrieve all the rows.

7.3 Transactions

7.3.1 Transactional Functions

The transactional functions are a special type of Wafl functions that have transactional semantics. A transactional function is essentially defined as a logical conjunction of a sequence of logical expressions - if all expressions evaluate the logical value true, then the transaction is committed and the result is true, otherwise, if at least one expression evaluates to false, the evaluation is canceled, the transaction is rolled back, and the result is false.

The definition of a transactional function begins with the keyword sequence [sql] transaction, followed by the sequence of action statements enclosed in curly braces and separated by semicolons. Just like with the queries, SQL is currently the only supported language.

Three kinds of action statements are supported:

  • SQL statements, that evaluate to true if successfully executed and to false in the case of failure;

  • regular logical Wafl expressions;

  • action statements, that modify the environment and return true if successful.

SQL Statements

SQL statements are regular SQL statements with no predefined limits. However, each driver and/or database can have some constraints.

Each driver must support insert, update and delete statements. Additionally, the statements for creating and dropping SQL objects, granting and revoking privileges and other statements can be supported.

The host variables are used in SQL statements in the same way as the queries.

Logical Expressions

Logical expressions are used as a kind of guards. If a logical expression evaluates to true, everything is assumed to be correct and the transaction resumes. If it evaluates to false, transaction is cancelled and rolled back.

Action Statements

…todo…

Example

The following transactional function insert a row in a book table and updates its pages count:

tAddBook( title, author, pages ) = sql transaction {
    insert into books( title, author )
    values (:title, :author);
    
    update books
    set pages = :pages
    where title = :title
      and author = :author;
};

7.3.2 …

Tuple elements are accessed via tuple selectors. As tuple elements are not named, their position is used to select them. The positions of the tuple elements are 1-based.

Tuple selectors use the dot-syntax <tuple>.<n>, where a positive integer indicates the position of the element to be read.

{# 1, 2, 3, 4, 5 #}.f()
where{
    f(t) = {#
        t.1,
        t.1 + t.2,
        t.1 + t.2 + t.3,
        t.1 + t.2 + t.3 + t.4,
        t.1 + t.2 + t.3 + t.4 + t.5
    #};
}

{# 1, 3, 6, 10, 15 #}

The selector indices and element types are checked in the type-checking phase so that it is not possible to use an invalid selector.

To support the syntax similar to records, the symbol ‘$’ can be used instead of the dot. The two syntaxes are equivalent - x$1 is the same as x.1.

Wafl Home            Downloads            Wafl Tutorial v.0.6.10 © 2021-2025 Saša Malkov            Open printable version