Object-Relational Persistence with Glorp
This document describes the Glorp object-relational mapper. By reading it, you will learn how to map your objects to a relational database. It presents step by step how to use Glorp and its basic and advanced concepts in a tutorial. At the end we include an Appendix that revisits relational database concepts. We thank the authors of the previous Glorp tutorials R. Withney and N. Pratt for having granted us the right to use some of their material. This document has been written by E. Maringolo and edited by S. Ducasse.
What is Glorp?
Working in a live object environment such as Pharo is great. You can freely create your domain objects and compose them as you like. Sometimes those objects can be stored in a way that preserves the original design but in other cases you have to store your objects in a Relational Database Management System (a.k.a. RDBMS). This requires you to flatten your object graph into relational tables managed by the RDBMS.
This process of mapping object to tables is called Object-Relational Mapping (ORM). It imposes certain constraints on the design of your object model to support persisting it in tables. Some models are easier to map onto tables than others, the difficulty lies in what is known as the Object-Relational Impedance Mismatch.
To work with relational databases, Pharo provides a battle-tested ORM created at CampSmalltalk a decade ago and maintained since then. Its name is Glorp for Generic Lightweight Object-Relational Persistence. It is usually called both GLORP (all caps, as an acronym), or Glorp (as a proper name).
Glorp is a full-featured ORM which offers a number of features to reduce the impedance as much as possible. Amongst those features, you'll find some features saving you from writing SQL queries by hand, managing transactions that rollback the changes to the objects in your image or commit them to the database, writing simple and complex queries using plain Pharo syntax, and other features that we will cover in this introduction chapter and in the advanced topics chapter.
Installation
Database server
Before installing Glorp you should already have installed the RDBMS of your choice on your machine or a reacheable server, it could be PostgreSQL (http://postgresql.org), MySQL (http://dev.mysql.com/), SQLite (http://sqlite.org), or any other of your preference (as long it is supported). Hereafter we will refer to this RDBMS of your choice as the Platform.
Database drivers
Along with your working installation of your RDBMS you should have installed in your image the database drivers required to connect to the server.
Native drivers
With Native drivers the raw protocol is written in Smalltalk and it uses Pharo sockets to write and read the data from the wire. This has the advantage of being self contained, because you don't depend on an external file or setup to connect to a server. Also, because everything is written in Smalltalk it is debuggable using Pharo tools down to a very low-level.
Library wrapper drivers (FFI)
Library Wrappers, as its name implies, wrap an underlying Operative System
shared library like a .dll
or .so
, these wrappers will access
the shared library functions by means of FFI
(Foreign Function Interface) calls and data structures.
Using an external library enables you to use an official client implementation of a proprietary protocol like Oracle, or you can be sure that even if the protocol spec is open you can use the official version of it. In order to use it in Pharo you need to implement the wrapper classes and methods.
A common driver API
There are plenty of drivers available in Pharo, however those drivers have different API's making their use not directly interchangeable. Making it hard to migrate to a different driver or to simultaneously support different RDBMS.
To solve that there is Garage (aka Garage Drivers), that provides a common API for the different driver implementations. Garage is Pharo's analogous to ODBC, JDBC or ADO.NET drivers.
Using this common API your code won't need to change if, for example, you decide to move from MySQL to PostgreSQL, as long as you don't use exclusive features, because all the implemented drivers will conform to the common Garage API.
Available drivers
There are many drivers available drivers in different versions of Pharo, as the time of writing this, these are the currently supported drivers are:
- P3 (Native PostgreSQL v3 protocol)
- Garage
- PostgreSQL (Native)
- MySQL (Native)
- UDBC SQLite3
Glorp with P3
P3 is a modern, lean and mean PostgreSQL client for Pharo.
P3Client uses frontend/backend protocol 3.0 (PostgreSQL version 7.4 [2003] and later), implementing the simple query cycle. It supports plaintext and md5 password authentication. When SQL queries return row data, it efficiently converts incoming data to objects. P3Client supports most common PostgreSQL types.
More information can be found at its repository. To load it first install it through Metacello.
Glorp packages for Garage
Glorp provides a Metacello configuration configured to load the core classes and/or its tests.
It may be the case that you want to load Garage in an alpha version, in such case, you should load the most recent version instead of a stable version that may be not defined for a alpha stream.
One package is the Glorp-Core
and there is also a Glorp-Tests
package.
Glorp with UDBC / SQLite3
Glorp may also be configured to work directly with the UDBC SQLite3 driver in Pharo 5 (instead of the Garage drivers):
Running the tests
Having loaded the database drivers and the Glorp packages, it is recommended to run the unit tests of Glorp, to ensure everything was loaded correctly and it is working properly.
Person: Our First Example Class
To put some of the concepts described previously into practice we will create
a Person
class and store it into a PERSON
database table,
all from within the Pharo environment.
Class definition
Let us define a simple class.

Defining a DescriptorSystem
As you can see the above created class and methods, don't have anything related to persistence and don't require you to inherit from a particular class.
Glorp models all the involved concepts (such as tables, columns, classes, etc.)
as first class objects, and then links instances of those objects in a DescriptorSystem
.
It is the core of a Glorp system, it holds all the Glorp metadata,
such as the Tables, Descriptors and Class Models.
By using a separate artifact (in this case, a class) to define all the metadata of your system, you can decouple your business models from their persistence information. This separation of concerns is a good practice, and helps with the maintainability of your code.
Also, having an orthogonal description of your domain objects allows you to have more than one descriptor for the same business domain class. This is an important difference with patterns such as ActiveRecord where the persistence metadata is defined in the business domain class, and you can't reuse the same class for two different systems using different persistence configurations.
Defining a DescriptorSystem subclass
All the definitions and mappings are defined in a subclass of
DescriptorSystem
, and for that we will create our own subclass as follows:
We said before that Glorp has a whole metamodel that involves describing the mapped class, the table(s) where it is going to be mapped and the mapping itself. To define each one of those, Glorp follows a convention in the method naming. We will mention the conventions below.
Class Model
We start by describing the class Person
, and the way to
do it is by defining a method with the pattern classModelForYourClass:
.
Here we describe the class model for the Person
class.
Note that a class model is a Glorp representation of your domain class. Glorp uses it to
store metadata about your domain objects. The descriptor system provides hooks to let you define
such a class model. classModelForPerson:
is one of these hooks. Based on the naming convention described
before, Glorp automatically associates this class model to your domain class.
Table Model
The class Person
will be stored in a single table (which is the usual case), and we
provide the description of the table in a similar way as with the class
model, i.e., by following the tableForYOURTABLE
convention.
Please notice the upper case of the table name.
Here is the definition of the table model for PERSON table.
If the table name is not uppercase, it is necessary to add
the method allTableNames
with the correct case, e.g.:
For a non-uppercase table names we define the method allTableNames
.
The serial datatype, explained in detail below, is an autoincrementing integer datatype. Every time you save a new instance Glorp will obtain a new integer and assign it to your instance.
Expressing mappings
Once we have the class model and the table object, we should define the mappings between the class attributes and the table fields.
In this simple example we use a DirectMapping
, which is a class of
mapping that takes the value in the column and assigns it to the attribute,
and vice versa.
Here is the definition of the mapping between the class and the table.
In the above method we can see how the the descriptor links the class attribute to the field name.
Although this is verbose and the advantages may not be visible in this simple example, here lies the power of the Glorp orthogonal descriptor system, you describe everything and then link all the parts the way you want without modifying your domain object.
Creating tables
Assuming we haven't created the database tables externally, Glorp's metamodel
allows you to perform DDL (Data Definition Language) commands such as
CREATE TABLE
or CREATE CONSTRAINT
(among others) using plain
Pharo objects, and it can even determine when to run those.
To do that we must first connect to the database. We will explain how to do so in the following sections.
We will be using a PostgreSQL server running on the same host as our Pharo image. We will then create the Login object and a DatabaseAccessor
for it to interact with the database.
Database Accessor
Glorp is programmed to be agnostic of the Smalltalk dialect and
database driver. To achieve that instead of talking directly to the
driver it uses an adapter object that acts as an intermediary between
Glorp and the underlying driver. Each database driver requires its
own subclass of DatabaseAccessor
.
We use the recommended accessor, which is the Garage accessor that is compatible with all the drivers supported by the library.
To define it as the default driver we must execute once the following expression:
When using P3, this is not necessary, instead execute:
Login
All the information necessary to connect to the database is defined in an instance of the
Login
class. It is an abstraction for the connection parameters
used to connect to the database server, like the Plaform used,
hostname, port, username, password, database name and other parameters.
Here is a login creation:
The database accessor requires an instance of the class Login
to establish a connection
to the database server specified in the login, using the platform specified.
You can make the Login
secure by making its password volatile, so
once the password was accessed the instance variable will be set to nil and
the secret won't be stored in the image anymore. Note that if you want to
use the same login instance for other connections, you must assign the
password string again.
Here we declare a secure Login
Verifying the connectivity.
If everything goes fine, then executing accessor isLoggedIn
should answer
true.
Another way to test it is performing a basic query like the following
(accessor basicExecuteSQLString: 'SELECT 3+4') contents first first
should
return 7 as the result; and yes, the contents first first
is dirty,
but it is not meant to be used this way, we're doing it just to quickly verify that everything is working.
Session object
Once we're able to connect to the database we can start interacting with it using
Glorp's objects. We will do that using an object that orchestrates
all the other objects, the GlorpSession
. The easiest way to get a
session is to ask the DescriptorSystem
passing the Login
instance
instance as the argument.
Here is how we access a session
How the Parts Play Together
Let us look into the implementation of the method DescriptorSystem>>sessionForLogin:
to understand
better how the parts interact.
First we get a system instantiating the receiver (in our case
GlorpBookDescriptorSystem
) passing the platform as argument.
The platform in the system is important because it will be used to
generate SQL code using its particular syntax and data types.
Then we instantiate a GlorpSession
and store it in the session object.
This session object is assigned a DatabaseAccessor
. It uses this
accessor to communicate to the database, hence the name, but to know what to
send to the db it needs all the metadata defined in the system object
defined before.
Note we ask the abstract DatabaseAccessor
class to return a concrete
instance for aGlorpLogin, it will return an instance of the
database accessor configured as default.
About the Platform.
In Glorp terminology a Platform is the RDBMS (for Relational Database
Management System) platform itself. The abstract class DatabasePlatform
defines the
abstract methods to deal with differences of supported data types,
test the support of certain features, and so on.
In Pharo we have
MySQLPlatform
, OraclePlatform
, PostgresPlatform
, SQLServerPlatform
, SQLite3Platform
and UDBCSQLite3Platform
as subclass of DatabasePlatform
.
Saving/Updating/Deleting Objects
It is time to put to work all the classes and schema we just created. To do it
we will create and persist a few instances of our example class Person
, and
then read some of them back. We'll explain a little bit
how Glorp decides what and when to INSERT, UPDATE or DELETE an object using
the concept of Unit of Work. But we should create the required tables first.
Creating Tables
Now we have our working session object, we can perform our DDL queries extracting the data from the system and sending commands to the accessor. But before we should execute the following to create the tables:
And voilà, it will send the required commands to the database to create our tables, sequences, constraints and so on.
Unit of Work and Transactions
If you worked with RDBMS's before, you know you can use transactions that detach you from immediate writes to the database files, and enable you to rollback (undo) your changes, or do a batch of changes and commit them all at once.
Glorp provides you with automatic handling of such database transactions. It also adds another concept, the Unit of work (aka UOW or UnitOfWork). This UnitOfWork keeps track of all the persisted objects, enabling you to commit or rollback changes at the object level while maintaining the database transaction in sync with it.
Saving New Instances
To save instances, we should send the message register: anInstance
to a session.
Note that this operation should occur within a Unit of Work using the message
inUnitOfWorkDo:
as follows:
As you can see, we didn't say if the instance needs to be inserted or updated, nor did we specify an id for each instance. The framework will determine that by registering the instance in the all-mighty session.
Note that if we are registering an object that was read in a previous unit of work, the object cannot be modified prior to registration in the current unit of work.
Reading instances
We can now read back some of the persisted instances. Again we use
the session object, sending it the message read:
and the class as argument.
This will return a collection of results.
The following expression returns all read all instances of Person
:
Of course you don't always read all the instances from the database but want to filter the results. For that you can pass a block that will act as a filter.
The following expression reads all instances of Person
matching a criteria
Reading one instance
Another convenient way to retrieve a single instance instead of a Collection
is to use the readOneOf:
or readOneOf:where:
message in an analogous
way to read:
and read:where:
.
About blocks
The read:where:
message might lead you to think you can pass any block to filter the results
as you can do with select:
in a regular collection, but that is not the case.
Glorp uses blocks as a very clever way to build expressions that are later converted to SQL by the framework. You can do basic filtering using this feature, but keep in mind it doesn't work as a regular block.
Translated selectors
Next to regular operators such as = and
>
=, you can use some special selectors
that get translated to their counterparts SQL operators.
For instance the similarTo:
selector gets converted to SQL's LIKE
operator.
With this we can retrieve all the Persons with a given last name using an operator that gets translated:
Object update
We can also update one instance, i.e., to read one and update it. We do it
in the context of a UnitOfWork using the message inUnitOfWorkDo:
.
Updating an object read within a UnitOfWork:
Note that to update an object accessed from the database does not require you to
send the message register:
to the session object,
as we did while populating the table.
This is because when you're working inside a Unit of Work all the objects you read are automatically registered by the session, saving you from explicitly registering them (which is safe to do anyway, but it is not required).
Object deletion
So far we covered Creation, Read, and Update. We're only missing the Deletion of
objects. This is as easy as it gets, just send the delete:
to the session
object with the object you want to delete as argument.
The following snippet show how to delete an object inside of a UnitOfWork:
Another way to delete objects is passing a condition block:
You can delete an object without being inside of a UnitOfWork, this will cause the object to be deleted right away, without giving you the option to rollback such action unless you handle the transaction manually.
Transactions
Glorp lets you handle transactions automatically using a unit of work or manually as we will now show.
Rolling back changes
Atomic modifications to a database are important, and to achieve that you need transactions that let you rollback changes if something fails.
A Unit of Work handles the errors and other unexpected terminations of the block, so if something fails, it not only rollbacks any changes at the database level if necessary, but also, and more importantly it reverts the changes in your objects.
The following example shows that the last name of the Locke object will not be modified and the database updated since the unit of work failed.
Manually managing transaction
The inUnitOfWorkDo:
message is a convenient way to isolate a block of execution
within the context of a transaction at both object and database levels. However
it has some limitations, like not being able to handle nested UnitOfWorks
or transactions. Everything happens in the context of the outer context transaction
in which it was evaluated.
If for some reason you need to handle the start/end/rollback of the unit of work manually, you can do so by using the following messages:
beginUnitOfWork
commitUnitOfWork
rollbackUnitOfWork
commitUnitOfWorkAndContinue
The first three have self-describing selectors, and can be used like in the following code snippet.
The message commitUnitOfWorkAndContinue
needs some explanation,
but the concept is simple: It commits the current unit of work, and then
creates a new one migrating all the objects registered in the commited unit of
work to the newly created, and still open, unit of work. If this paragraph
confuses you, looking at its implementation might explain it better.
It is useful for cases like batch loads or updates, where you want to commit changes every n instances or similar.
Commiting and continuing the Unit Of Work:
We can cleanup (delete) some sample instances by running:
Glorp Configurations and Additional Concepts
In our previous example we created a simple class that mapped 1:1 with a table using simple data types, but Glorp provides many more features than an ActiveRecord like mapping. It lets you fine tune the persistence of your classes. We will go over the different configurations of class models, table data types and constraints and mappings of all sorts.
Class models
A class model defines the attributes of your domain objects, and how they
compose. Each of your persisted objects should have an instance of
GlorpClassModel
in the descriptor system containing all the
attributes of your class. By convention, these attributes are added by
implementing classModelForYourClass:
as we did previously for the Person example.
The simplest way to add an attribute is using a series of convenience methods returning instances of GlorpAttributeModel
,
described below:
newAttributeNamed: #email
- Used to define simple scalar/literal values such as Numbers, Strings, Dates, etc.
newAttributeNamed: #address type: Address
- Used to define 1:1 (one to one) relations with other objects of your domain model.
newAttributeNamed: #invoices collectionOf: Invoice
- Used to define 1:n (one to many) and n:m (many to many) relations of your class model with other models.
newAttributeNamed: #invoices collection: collectionClass of: Invoice
- Similar as the one above for 1:n and n:m relations, but you can define what kind of Collection is going to be used.
newAttributeNamed: #counters dictionaryFrom: keyClass to: valueClass
- Used to define an attribute that is a Dictionary where the key is
keyClass
and its values are instances ofvalueClass
.
Attribute properties
The above described methods return instances of GlorpAttributeModel
, which
share common properties that you can configure using the following messages.
useDirectAccess: aBoolean
- Let you define whether the access to the attribute described by the symbol of your domain model will be performed by directly accessing the instance variable (slot) instead of using a message send. This is the default, and lets you persist variables that don't have getters and setters. It's also useful where the getters and setters
have extra behavior that you don't want triggered by the persistence mechanisms. On the other hand, going through a message send allows you to persist things that don't correspond directly to a single instance variable.
beForPseudoVariable
- Useful for cases where you want to describe an attribute that won't be read nor written, but still described to be used on queries.
For example, in a later section we talk about mapping the position of an object in a collection to a field in the database, since databases don't maintain order. We could map that order field to a pseudo-variable and use it in queries, even though we don't want it stored in the object.
Table model
Glorp also models your database objects, such as tables, constraints, indexes, etc.. With this model it will be able to determine how to serialize the objects to SQL, how to perform joins to retrieve 1:1 or 1:n relations, and so on.
The descriptor system follows a convention to define the tables,
it uses the tableForTABLENAME:
selector to configure TABLENAME
, the
argument of this method is an instance of DatabaseTable
, and this
method is responsible for describing your table in the relational
database, including field names and their data types, contraints (primary keys
and/or foreign keys), etc.
Adding fields to the table
Let's bring back our example from the beginning of this book
As you can see, you can add a field by sending createFieldNamed:type:
to
the table object. The first argument of the method is name of the field
(aka column) in your table, the second one is the datatype.
For the datatype we're not specifying any particular implementation of the type but instead we send a message to the platform object, and in Glorp jargon the platform is the RDBMS we'll be using.
Doing it this way enables our table model to be RDBMS agnostic, and, for instance,
it will work with SQLite or PostgreSQL (or any other platform).
E.g. if our platform is PostgreSQL then platform varchar
will return
VARCHAR
, but if instead our platform is SQLite then it will return TEXT
because SQLite only supports TEXT
as datatype for character based fields.
Going back to createFieldNamed:type:
, it will return an instance of
DatabaseField
, which has properties of its own, such as if the field is
nullable, unique, its default value, and some convenience methods such
as bePrimaryKey
which will create a PK (primary key) on the table
for this field.
Commonly used datatypes in alphabetic order are:
| selector | SQL-92 datatype | Pharo class |
| blob | BLOB | ByteArray |
| boolean | BOOLEAN | Boolean |
| date | DATE | Date |
| decimal | NUMERIC | ScaledDecimal |
| double | DOUBLE | Float |
| float | REAL | Float |
| integer | INTEGER | Integer |
| serial | SERIAL | Integer |
| time | TIME | Time |
| timestamp | TIMESTAMP | DateAndTime |
| varchar | VARCHAR | String |
You can find more datatypes by browsing the types method category of
DatabasePlatform
or any of its subclasses, not all databases support all
datatypes, and some platforms have their own datatype.
Mappings (aka Descriptor)
Once we have the Class Model describing our business objects and the
Table Models describing the tables where they will be stored, we need to
describe how they are going to work together. To achieve that Glorp uses
an instance of Descriptor
that contains the mappings between the attributes
of the class model and the fields of the tables involved.
As expected, there is a convention to define Descriptors and, you guessed right,
it is descriptorForYourClass:
that will receive as an argument a
instance of Descriptor
on which you will configure the mappings.
As you can see, the usual practice is to get a reference to the tables involved
(one in this example, could be more), and then add mappings to
aDescriptor
by sending it the message newMapping:
with an instance of
the class of mapping we want to use.
There are many mappings that Glorp provides, but the most used ones are:
Direct mapping.
To map a number, a string, a date or any other value like object
you use an instance of DirectMapping
, specifying which attribute from
your class model you want to map to a field of a table.
Although many times they will be the same symbol, the argument you pass to
the from:
parameter is not the symbol of the selector
but instead the name of attribute you have defined in your classModelFor...:
method.
1:1 relationships.
When you're trying to map a single reference from one object to another,
you use the class OneToOneMapping
and specify the name of the attribute
that this instance is mapping. If everything is as simple as this you don't
have to specify further instructions and Glorp will determine how to retrieve
the instances based on the information provided in the ClassModel, foreign keys,
etc. If you need further instructions you can always define them, more on that later.
1:N relationships.
If you are trying to map a collection that belongs to a parent/owner object
like the invoices of a Person or the items of such invoice to the invoice itself,
then you have to use the OneToManyMapping
class.
It is commonly used when you have the rows of the tables at the N side of
the relation have a field that points back to the the 1 side.
In our example, the invoices have a Foreign Key pointing back to the persons table.
N:M relationships.
The Many to Many relationships is similar to the previous one, but in this case
the other side of the relation might belong to more than one owner.
For instance, if you have Person and Tag, a Person might have many Tags,
but these tags in turn belong to other instances of Person.
In this case you use the ManyToManyMapping
and also have to specify
the attributeName.
From your the Pharo point of view, you will continue to see a regular collection
but when storing its elements it will require a third table to do it, called
a link table, Glorp has a convention to name such link tables, and it is
based on composing the names of both sides of the relations.
Following our Person to Tag relation, the table name Glorp will expect is
TAG_ON_PERSON
, and this must also have its definition in the descriptor system,
so in this case there must be a tableForTAG_ON_PERSON:
method.
Common properties of relationship mappings
All OneToOneMapping
, OneToManyMapping
and ManyToManyMapping
are
convenience subclasses of RelationshipMapping
, and as such they share
common properties that can be modified.
For instance, the only difference between OneToManyMapping
and
ManyToManyMapping
is that the later is initialized to use a link table,
but you can have a OneToManyMapping
that uses a link table if you prefer,
you simple send useLinkTable
to it.
Reference Classes.
All RelationshipMapping
instances can specify the class they reference
by sending referenceClass:
with the desired class as argument. This is
used a lot, and is a convenience method that ends up modifying the attribute
in the ClassModel. So you can specify it at either place you find more
convenient.
Note we only define the class referenced, because the other side of the relationship is the class for which we're defining the mapping itself.
Other attribute common to all RelationshipMapping
subclasses is that
you can specify how it is suppose to join with other tables, it is,
its Join
object. More on this on the advanced section.
Exclusivity.
Another attribute is the exclusivity of relationships, enabled by sending
beExclusive
to the mapping. This handy method makes that once the parent
(or left) side of the relation is deleted from the database, Glorp will
manually delete the child (or right) side of it.
In our Person example, if the relation with Address
was exclusive,
then deleting a Person would cause Glorp to delete its address as well.
One caveat of this convenience approach, is that it doesn't play along with database
foreign key actions like ON DELETE CASCADE
, basically because one will
happen without the other knowing it, and Glorp will try to delete an
exclusive object that was already removed from the database
by the CASCADE and because of that it will fail.
Collection type.
In the case of to many mappings you can specify the collection type
of the mapping, for instance, you can send collectionType: Set
to your
mapping and have it initialized to a Set instead of the default OrderedCollection
.
Proxy.
And last, but not least, you can specify whether to proxy or not such a relation.
By sending shouldProxy:
either true
or false
, Glorp will
place proxies or immediately build an instance from the database. If you don't specify this, it will proxy by default.
A Word about Proxies.
To avoid reading the whole object graph every time you read an object that has any kind of relationship mapping, Glorp places a Proxy object instead of reading it from the database. This saves a lot of roundtrips to the database, bandwidth and CPU/memory in the image building objects that you may not ever use.
However, there are cases when you know that certain objects reference other objects that you know that always will be used, if that is the case you can avoid the proxying, and instead force the immediate materialization.
Once Glorp turns a proxy into one of your domain objects, it will store your object in the Session Cache (more on this later), so future references to the same object won't cause an additional read from the database, and instead use the instance in the cache.
And also, Proxies are tricky animals, so beware when debugging something that involves them. Sometimes they can fool the development tools, making them believe they're something they're not!
Extending our Basic Example
Now that we explained in more detail the main parts of Glorp's descriptor system, we're ready to extend our basic example, we're going to create a minimal invoicing model to include the concepts just learnt.
We will
- define new Pharo class
- describe such new classes to be handled by Glorp
- describe the table in which such class instances will be stored
- describe the mapping between the instances and the records.
Domain classes
To do so we will create the following classes Person
,
Address
, Invoice
, InvoiceItem
, all inheriting from
GlorpBookObject
, as a convenience to factor the id attribute.
We will omit the listing of accessor methods, but you should create them. Regarding the id attribute, Glorp doesn't require you to use it as the primary key of your objects.
You can use as many attributes as you want, but for the sake of simplicity we will use it as a surrogate key, which is a key that is a unique identifier, an object not derived from application data.
Here is the hierarchy root of book classes.
Aside from the accessors, when you're dealing with collections (to-many relations) that are going to persisted, it is recommended to early initialize all the instance variables that will reference such collections. Otherwise it could cause some failures because Glorp keeps a copy of the object attributes to enable rolling back changes on memory. Plus... having a complete instance is a good practice too.
Class Model Declarations
Let's create our GlorpBookDescriptorSystem
to describe the new
domain models.
Table Declarations
Then we should declare the tables that represent our domain.
Here is the table models for the class Person
, Address
and Invoice
.
As you can see in our INVOICE table, we not only added fields, but also
added Foreign Keys (aka FK)
to our table table model using the message addForeignKeyFrom:to:
.
This will add the foreign keys to the list of constraints of the table model, and will be used by Glorp to order writes/deletions and
infer relations.
Also notice that we need to pass the referenced field as argument.
To obtain it we ask the descriptor system for the table of PERSON
and ADDRESS by sending tableNamed:
with the name of table we want
as argument. This will return the DatabaseTable
with that name,
and to this object we will ask for the field named id.
As you can see in the last method, we created a table model for the table
PERSON_ON_ADDRESS
. This table will be used as a link table. Because
this link table references to other tables we also created Foreign Keys
from each reference field using the same approach described before.
Mapping declarations
Address
The mapping for the class and table address is simple since there is a direct mapping between the class model (the domain model) and the table.
Invoice
The mapping for invoice is a bit more elaborated, because it not only
contains Direct Mappings, and one-to-one mappings for the Person
and
Address
, but also contains a one-to-many relationship to its items.
We introduced the orderBy:
and writeTheOrderField
attributes in
the #items mappings. You can use the first attribute independently or both together.
The orderBy:
attribute instructs
Glorp to read the elements from the database and order (aka sort) them
by the field described in the block. This is useful so the collection that holds
the references to the items (in this example) is always sorted the same way,
because the order the rows come from the database is not always the same,
and you can't trust they will return in the same order as they were written.
The writeTheOrderField
command will make Glorp save the index of the
element in the collection as a field in the row. It will use the field defined
in orderBy:
as its target, in this case position. So if you have an
OrderedCollection with five items, the first element one will have 1
stored
in the position field, the second 2
, and so on. If you change the order
of the elements in the collection, the position field will be saved with the
new index.
InvoiceItem
The mapping for each invoice item is slightly different since an item is always
part of an Invoice, and only that invoice; so because of that we have a
OneToOneMapping
referencing its invoice. This way we make the relation
between Invoice and InvoiceItem bidirectional.
Person
The mapping for the class Person is more elaborate and worth some explanation of what happens in the last lines.
Here we introduce the explicit join:
attribute of the Reference Mapping.
Usually Glorp infers and creates such Join by looking at the Foreign Keys
of the described or referenced class, but you can make that Join explicit,
you just have to create a Join
object with the source field and the
referenced field as arguments. In this case the join is pretty straightforward
and between two fields, but it could be between as many fields as you want.
Sample data
We will create some sample data for our example, and for the Persons we will save three instances named after famous Smalltalkers, with random addresses, so don't mail them.
Now we created the sample Persons, let's add an extra address for Alan.
As you can see, we didn't have to register the newly created address because the Person was read inside of a UnitOfWork. If we read the instance again and inspect its addresses we will find the new instance is there.
Before continuing and to make code simpler and more readable we'll create a few convenience methods:
We can now procceed to create an few instances of Invoice. Let's create one invoice for each person in the database with two items describing donations to the Pharo Consortium and Association with randomized amounts (within a certain range) for each one.
As usual, you can read the Invoices by doing session read: Invoice
, let's
print how much each Person donated.
When we presented the ReferenceMappings we mentioned how Glorp puts proxies in place of a referenced object to avoid performing unnecessary roundtrips to the database.
In the example above, assuming all caches are empty, Glorp will perform one query to retrieve all the Invoices and then one extra query to retrieve the data to instantiate each Person. So if you retrieve Invoices for three different persons, Glorp will perform four queries, it is four roundtrips, to the database. Keep that in mind while we continue with other examples.
The message sum:
above will retrieve all the rows from the tables,
instantiate an equivalent quantity of Invoice and then go over all
the invoices' items to sum them. In our example, we only have a few instances
but what if we had a million of them? Instantiating everything at the Pharo
side wouldn't be convenient for such a simple sum. Performing an SQL SUM()
at the database side would be more efficient, since we would only retrieve
the total instead. But how to do such a query?
The Query object
So far we've been querying objects by means of sending read:
or
read:where:
to the session object, but if you want to perform
a particular query, like limiting the number of results, running an aggregate
function like SUM()
, AVG()
, ordering the rows at the server side,
performing subqueries and many other features then the session object
doesn't provide you with an API for that.
The read: message
Everytime you sent a read:
message to the session, the session created a
simple Query
object to read the class passed as parameter as in the following expression.
But you can instantiate a Query object independently, configure it to fit your needs and
execute it in the session. Enter the Query
object!
SimpleQuery
Let's say we want to know the sum of all the prices of our InvoiceItems,
we can do that by using the retrieve:
method and using an aggregate function
like sum
.
The retrieve:
method configures the query to return only the retrieved attributes
of the read object. The query will return a collection with the objects
retrieved instead of a collection of instances of the read class.
If we know beforehand that our query will return a single row with a single
field, i.e., a single object, we can configure the query to return that
object directly by setting readsOneObject:
to true.
If you retrieve more than one object then the query will return a collection of collections, in the form of #(#(...) #(...) ...).
Attributes of queries
The Query object models an SQL SELECT
statement, and like that statement
it can include a WHERE
, ORDER BY
, GROUP BY
, HAVING
, etc.
WHERE expression
You can always include a where expression as you did when reading through the session object. Inside the where expression, you can filter by attributes of referenced objects.
Or even by interacting with a referenced collection, i.e., retrieving only Invoices containing items with a price above 2000.
ORDER BY expression
The ORDER BY
expression in a Query object is set by sending orderBy:
to
the query object as many times, as fields you want to include in your ordering
criteria.
GROUP BY expression
As shown in previous code samples you can also perform SQL's aggregate
queries like SUM()
, COUNT()
, etc.; if you want to perform those
aggregate functions grouped by certain expression, then the groupBy:
option comes to the rescue.
In the above example you can see we're retrieving two fields concatenated and grouped by the same concatenated value. Remember that when using an aggregate function, all the non-aggregated fields retrieved must be included in the GROUP BY. Notice here we're also grouping by a field in other table, Glorp resolves all the JOINs for you.
Retrieving the average donation
Although the GROUP BY expression is used extensively in production systems, it only works for retrieving fields of the query and not full-fledged instances of your objects described in your DescriptorSystem.
LIMIT and OFFSET expressions
You can also limit the number of rows returned by your Query, this is useful if you know your query will return one single row/object or if only want to retrieve a certain number of rows. The LIMIT expression varies between different databases, Glorp will make sure it generates the right SQL code for the plaform you're using.
When the LIMIT expression is used in combination with the OFFSET expression it lets you implement simple pagination queries.
Fetching referenced objects
We mentioned before that when you're querying the Invoices Glorp will place a Proxy object for each reference (e.g. Person) it doesn't have in the session cache, and then once the Proxy is hit by a message send it will materialize it by retrieving its contents by performing a database Query.
Imagine this scenario for at least 1000 Invoices that reference a different Person each, that would be 1001 queries and their resulting I/O impact.
In a regular SQL query you would solve that by doing a regular JOIN with the related table; when reading first class Objects with Glorp it makes it almost as easy as doing a JOIN in SQL.
You can specify as many alsoFetch:
references as you want, and Glorp
will knit all together by using JOIN expressions created from the Join
objects defined when configuring the reference mappings of the Class Descriptor.
If nothing else is added to the alsoFetch:
expression block it will perform
INNER JOIN
to knit the relations. If you're also fetching a
reference that might be nil then the whole object/row would be excluded
from the result. For instance, if an Invoice doesn't have an Address, then
with the above example that invoice would be left out by the JOIN.
You can save that by doing an OUTER JOIN
on the field you're fetching
by simply adding asOuterJoin
to the field.
Retrieving all at once with an OUTER JOIN
The alsoFetch:
is a powerful feature, and can be used in combination with
all the above described Query attributes (except for GROUP BY).
Ensuring fresh data
Everytime you read an object inside a Glorp session, it will save it in the
session cache keyed by an object representing the Primary Key in your database,
in our example each ClassDescriptor will have a cache keyed by the id
field,
which happens to be our Public Key in the tables.
This is great because, as any proper cache would, it avoids going outside of your image to retrieve something from the database, saving you from the extra roundtrips. However, when the objects are modified concurrently during the lifetime of your session, then you could end up with an old version of it, and even if you perform a new Query to the database Glorp will try to use the same object it had in the session Cache.
As you might expect by now, Glorp provides a way to override this and force
the update of the Cache with the newly read data. Just simply set
shouldRefresh: true
, and this will tell Glorp to ignore any attempt
to read from the Cache and instead always use what is new.
Set operations
In addition to statements that affect the result set of the query,
you can perform
set operations
like UNION ALL
, INTERSECT
and EXCLUDE
(aka subtract)
between your query and other queries.
| SQL SET OPERATOR | Query selector | |
UNION ALL |
union: |
|
INTERSECT |
intersect: |
|
EXCLUDE |
minus: |
Conclusion
The SimpleQuery offers powerful logic and you should play with it to understand how to use it.
Handling inheritance
In this chapter we will look at how Glorp help us handling object of different subclasses.
When a class Parent
has two subclasses LeftChild
and
RightChild
, a polymorphic query is one where we can query on the
Parent
class and as a result get instances of Parent
, LeftChild
and RightChild
.
There are three common ways to support polymorphic queries with a relational database:
- A table for each class (abstract or not)
- A table for each concrete class
- One table for all classes
Glorp currently supports the two latest options. We will illustrate these two approaches now.
A table for each concrete class
In this approach each concrete class has its own table.
That table holds all the instance variables for the class.
As an example of this approach, let us imagine that the Parent class an abstract class.
Therefore, we need two tables, which we call LEFTCHILDTABLE
and
RIGHTCHILDTABLE
.
Class definitions
The class definitions are straightforward.
System Description
We'll extend our System Descriptor to include the newly created classes.
Class model for LeftChild
.
Class model for RightChild
.
Describing the tables.
We introduced sequenceTypeNamed:
, that we didn't use before. When using
sequence
or serial
datatypes, Glorp will create a database sequence
for each table if required, but because we're using a type resolver
with inheritance, it is necessary that all the children classes that use
a serial
datatype share the same sequence.
The message sequenceTypeNamed:
lookups in the Descriptor System for an existing sequence with that name,
and if no one was registered before it will instantiate a new one with that name and register it for later use.
Handling inheritance
So far we created the class models for two classes, and a table model for each
one of them, but nothing involving inheritance. Notice we don't have to create a
class model for the Parent
class, although we will have to create a
class descriptor for it.
As you can see, we introduced a concept in the Class Descriptor that we never used, explicitly, before: a Type Resolver (or resolver for short).
Type resolver
Every Class Descriptor has a resolver that helps it determine which
class to instantiate. If Glorp doesn't find one explicitly defined, it
creates an IdentityTypeResolver
for your class.
You can define the resolver for your class by implementing a method
named typeResolverForYourClass
that returns an instance of TypeResolver.
We will create one for our class Parent
There are different classes of resolvers:
- IdentityTypeResolver
- No inheritance
- HorizontalTypeResolver
- Each Class is persisted in a separate table
- FilteredTypeResolver
- All Classes are persisted in the same table
We chose the HorizontalTypeResolver
because we're storing our instances
in separate tables. IdentityTypeResolver
is the default one: it is for the case when there is no inheritance,
where you map a class to a single table, or more than one table but always for a single class.
and we will cover FilteredTypeResolver
later.
what is IdentityTypeResolver
Each of the class descriptor methods register a resolver.
The method typeResolverFor:
results in a call to typeResolverForParent
.
If you look at the last line in the definition of Descriptors for LeftChild
and RightChild
you'll notice we're registering the Descriptor in the
Type Resolver of the Parent
class; both child classes, unless overriden,
will continue having their default resolver.
Playing with instances
Now we can play with some sample instances
With the setup above, we can write queries like the following one which
query the Parent
class:
We can still write queries directly on the subclasses
There is one restriction on polymorphic queries when using
HorizontalTypeResolver
(a table for each Concrete Class) which is one can
not use orderBy:
. The query makes multiple requests to the database as it
has to read both tables, so we cannot have the database order the results
for use. As a result the following request will raise an exception becausehorizontal resolvers can't order.
One table for all classes
In this alternate approach for table organization, there is one table that holds the data
for all objects in the hierarchy. In this example the one table is called
ALL_DATA
. We include an object_type
column used to store the type
of the object in the row. This allows GLORP to create an instance of the
correct class when it reads a row in the table. Note
that regardless of what type of object is in a row, one column,
either left_value
or right_value
, will be null
.
While this method does waste space in the database, performing a polymorphic
query is faster as there is only one table to access.
We start by describing the new Type Resolver for the Parent
class.
The class definitions remain the same as in the Table for each concrete class example. The descriptor changes a bit.
Since we use a different resolver, we need to supply values for object_type
column and need more information for the Parent
class.
Note how to indicate the value of the object_type
column for
LeftChild
objects with the resolver.
The argument for the keyedBy:
parameter is the String 'L'
but it could be any String of your preference, like the class name. Keep in mind that the shorter the key, the less storage space it will take, and also less bandwidth used.
Even though Parent
is still an abstract class, Glorp requires us to given
the mappings for the Parent
instance variables. If Parent
were not abstract
we would have to provide a key value for the column object_type
.
About inheritance with Type Resolvers
Although in our example with the class Parent
was the superclass of both
LeftChild
and RightChild
, in Glorp terms it is not required that
child Descriptors share a common class ancestor, as far as they have the same
class attributes as the parent Descriptor.
Under the hood
How Glorp expressions work
At this point you might have noticed that we use blocks in many places to specify the where expression, retrieve a particular field, order the results, and many other things.
Although we use blocks for the convenience and readability they provide, once assigned to a Glorp object such as
SimpleQuery
, Glorp converts the block to a Glorp expression, depending
on the context it may be any of the subclasses of GlorpExpression
.
To convert the block to an expression, Glorp passes a MessageArchiver
as the
first argument of the block, the message archiver will intercept all the message sends vía doesNotUnderstand:
hook,
and will return another archiver, so the next message send is handled by the
new archiver and so on. With this strategy it will construct a tree
of instances of GlorpExpression
.
Using each as the block argument name can be misleading, but it is used
by convention, but it has more sense in the context of a filtering expression
than in a retrieve:
or groupBy:
.
Because it uses doesNotUnderstand:
as a way to build the expression tree,
it is possible that some selectors will be highlighted by Pharo as not
implemented, in particular salectors that are used to instantiate
FunctionExpression
's.
About special selectors
When writing blocks that involve more than one condition, we don't use
and:
nor or:
selectors, and instead we use AND:
and OR:
respectively. The same applies for isNil
and notNil
. This is so
because the compiler optimizes certain message sends, including the ones
mentioned before.
Example of special selectors:
Function expressions
The special selectors mentioned before are nothing but a particular case of a
FunctionExpression
, but as those there are many others, consider the
following Glorp query:
You can see there that we send the asUppercase
message to the lastName
attribute and then similarTo:
to that result. The previous query will produce something like this:
It is very likely that the similarTo:
selector will be highlighted
as missing or not implemented in the current image. This is so because
the MessageArchiver
will resolve the symbol to an instance of one
of the subclasses of FunctionExpression
. It achieves it by rebuilding
the archiver as it consumes the selectors, you can find how it does by
looking for the implementors of rebuildOn:startingFrom:withOuterScopeBase:
.
In the middle of those implementors you'll find senders to
getFunction:arguments:
, this in turn will query the platform
for the list of functions available, which will end up creating
the list of default functions by sending createBasicFunctionsFor:
in to the FunctionExpression class
.
If you ever need to add functions not supported by default, you can extend
the functions in FunctionExpression class >> createBasicFunctionsFor:
or by adding them to your current platform. Also you can read there the list
of available symbol functions and their default mapping to SQL functions.
Glorp class model diagram
The following diagram provides an outline of the most important classes and attributes of Glorp's object model.
Appendix A: Basic Relational Databases Concepts
Tables
A table is a collection of related data held in a structured format within a database. It consists of fields (columns), and rows.
In relational databases and flat file databases, a table is a set of data elements (values) using a model of vertical columns (identifiable by name) and horizontal rows, the cell being the unit where a row and column intersect.
A table has a specified number of columns, but can have any number of rows. Each row is identified by one or more values appearing in a particular column subset. The columns subset which uniquely identifies a row is called the primary key.
Rows
In the context of a relational database, a row —also called a record or tuple— represents a single, implicitly structured data item in a table. In simple terms, a database table can be thought of as consisting of rows and columns or fields. Each row in a table represents a set of related data, and every row in the table has the same structure.
Columns
For example, in a table that represents companies, each row would represent a single company. Columns might represent things like company name, company street address, whether the company is publicly held, its VAT number, etc.. In a table that represents the association of employees with departments, each row would associate one employee with one department.
Constraints
Constraints make it possible to further restrict the domain of an attribute. For instance, a constraint can restrict a given integer attribute to values between 1 and 10. Constraints provide one method of implementing business rules in the database. SQL implements constraint functionality in the form of check constraints.
Constraints restrict the data that can be stored in relations. These are usually defined using expressions that result in a boolean value, indicating whether or not the data satisfies the constraint. Constraints can apply to single attributes, to a tuple (restricting combinations of attributes) or to an entire relation.
Since every attribute has an associated domain, there are constraints (domain constraints). The two principal rules for the relational model are known as entity integrity and referential integrity.
Primary Key
A primary key uniquely specifies a tuple within a table. In order for an attribute to be a good primary key it must not repeat. While natural attributes (attributes used to describe the data being entered) are sometimes good primary keys, surrogate keys are often used instead.
A surrogate key is an artificial attribute assigned to an object which uniquely identifies it (for instance, in a table of information about students at a school they might all be assigned a student ID in order to differentiate them).
The surrogate key has no intrinsic (inherent) meaning, but rather is useful through its ability to uniquely identify a tuple. Another common occurrence, especially in regard to N:M cardinality is the composite key. A composite key is a key made up of two or more attributes within a table that (together) uniquely identify a record. (For example, in a database relating students, teachers, and classes. Classes could be uniquely identified by a composite key of their room number and time slot, since no other class could have exactly the same combination of attributes. In fact, use of a composite key such as this can be a form of data verification, albeit a weak one.
Foreign Keys
A foreign key is a field in a relational table that matches the primary key column of another table. The foreign key can be used to cross-reference tables. Foreign keys do not need to have unique values in the referencing relation. Foreign keys effectively use the values of attributes in the referenced relation to restrict the domain of one or more attributes in the referencing relation.
Indexes
An index is one way of providing quicker access to data. Indices can be created on any combination of attributes on a relation. Queries that filter using those attributes can find matching tuples randomly using the index, without having to check each tuple in turn. This is analogous to using the index of a book to go directly to the page on which the information you are looking for is found, so that you do not have to read the entire book to find what you are looking for.
Relational databases typically supply multiple indexing techniques, each of which is optimal for some combination of data distribution, relation size, and typical access pattern.
Indices are usually not considered part of the database, as they are considered an implementation detail, though indices are usually maintained by the same group that maintains the other parts of the database. It should be noted that use of efficient indexes on both primary and foreign keys can dramatically improve query performance.
Relations
Joins
A SQL join clause combines records from two or more tables in a relational database. A JOIN is a means for combining fields from two tables (or more) by using values common to each
ANSI-standard SQL specifies five types of JOIN: INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER and CROSS. As a special case, a table (base table, view, or joined table) can JOIN to itself in a self-join.
INNER JOINs
An inner join requires each record in the two joined tables to have matching records, and is a commonly used join operation in applications but should not be assumed to be the best choice in all situations. Inner join creates a new result table by combining column values of two tables (A and B) based upon the join-predicate. The query compares each row of A with each row of B to find all pairs of rows which satisfy the join-predicate. When the join-predicate is satisfied by matching non-NULL values, column values for each matched pair of rows of A and B are combined into a result row.
The result of the join can be defined as the outcome of first taking the Cartesian product (or Cross join) of all records in the tables (combining every record in table A with every record in table B) and then returning all records which satisfy the join predicate. Actual SQL implementations normally use other approaches, such as hash joins or sort-merge joins, since computing the Cartesian product is slower and would often require a prohibitively large memory space to store.
OUTER JOINs
The joined table retains each record—even if no other matching record exists. Outer joins subdivide further into left outer joins, right outer joins, and full outer joins, depending on which table's rows are retained (left, right, or both).
The result of a left outer join (or simply left join) for tables A and B always contains all records of the "left" table (A), even if the join-condition does not find any matching record in the "right" table (B). This means that if the ON clause matches 0 (zero) records in B (for a given record in A), the join will still return a row in the result (for that record)—but with NULL in each column from B. A left outer join returns all the values from an inner join plus all values in the left table that do not match to the right table, including rows with NULL (empty) values in the link field.
For example, this allows us to find an employee's department, but still shows employees that have not been assigned to a department (contrary to the inner-join example above, where unassigned employees were excluded from the result).
Data manipulation language (DML) queries
SELECT
A SELECT
statement retrieves zero or more rows from one
or more database tables or database views. In most applications, SELECT is the
most commonly used data manipulation language (DML) command. As SQL is a
declarative programming language, SELECT queries specify a result set, but do
not specify how to calculate it. The database translates the query into a "query
plan" which may vary between executions, database versions and database
software. This functionality is called the "query optimizer" as it is
responsible for finding the best possible execution plan for the query, within
applicable constraints.
INSERT
An SQL INSERT
statement adds one or more
records to any single table in a relational database.
The number of columns and values must be the same. If a column is not specified, the default value for the column is used. The values specified (or implied) by the INSERT statement must satisfy all the applicable constraints (such as primary keys, CHECK constraints, and NOT NULL constraints). If a syntax error occurs or if any constraints are violated, the new row is not added to the table and an error returned instead.
UPDATE
An SQL UPDATE
statement changes the data of one or more records in a
table. Either all the rows can be updated, or a subset may be chosen using a
condition.
In some databases, such as PostgreSQL, when a FROM clause is present, what essentially happens is that the target table is joined to the tables mentioned in the fromlist, and each output row of the join represents an update operation for the target table. When using FROM, one should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.
Because of this indeterminacy, referencing other tables only within sub-selects is safer, though often harder to read and slower than using a join.
DELETE
In the database structured query language (SQL), the DELETE
statement removes one or more records from a table. A subset may be defined for
deletion using a condition, otherwise all records are removed.
Any rows that match the WHERE condition will be removed from the table. If the WHERE clause is omitted, all rows in the table are removed. The DELETE statement should thus be used with caution.
The DELETE statement does not return any rows; that is, it will not generate a result set.


'I'm on meth': South Dakota commercial kicks off campaign with alarming message
by AP"I'm on meth."
A commercial airing in South Dakota has a startling tagline that's grabbing viewers' attention.
It's part of a nearly $1.4 million campaign aimed at tackling the state's methamphetamine epidemic, which includes $449,000 paid to the Minneapolis-based ad agency that created the tagline.
The commercial, which features several people saying 'I'm on meth," drew a few snide jokes and crass comments online. Several are outright mocking the ad.
One Twitter user posted, "It seems that if I'm on meth, I can live to a ripe old age, eat in restaurants, play football with my pals, and have a family. That sounds much better than the life I'm having now. Somebody get me some meth."
According to the state's Department of Social Services, the number of 12- to 17-year-olds who reported using meth in the last year is double the national average. The state has also seen the number of people seeking treatment for meth addiction double from 2014 to 2018.
"Meth is not someone else's problem. It's South Dakota's problem," a narrator says. "We need everyone to get on it."
Gov. Kristi Noem defended the campaign, posting, "Hey Twitter, the whole point of this ad campaign is to raise awareness. So I think that's working ..."
Noem appears in a commercial for the campaign and says South Dakota's meth problem is "growing at an alarming rate."
The images featured the words, "Meth. I'm on it." over photos of people in cowboy hats, in a coffee shop or on a football field.


Belfast city centre 'can become hotspot for FMCG market'
BELFAST city centre is primed for further growth and investment in the fast-moving consumer goods (FMCG) market, according to a Centra franchise owner.
Ricky and Vicky Leathem have invested £1.5 million across their four stores since starting the business in 2007.
And having recently switched their banking to Danske Bank, they say they now able to facilitate further growth in the retail market, with the bank providing working capital requirements and a £150,000 loan to refurbish their new Bradbury Place store.
Ricky Leathem said: “Belfast city centre has an opportunity to grow its FMCG market in line with a number of new office spaces coming to market. There are more people working and living in the centre and that is one of the main reasons we opened a new store at Bradbury Place.
“Our new store differs from our other three as it has a sit in area and a different retail offering. We created 17 new jobs when we opened a few weeks ago and that number will increase as the store becomes busier. I always have an eye on potential sites for new stores. If it makes sense as a business decision, we'll consider it.”
He added: “The challenge is that the price of food is always rising and there is still so much uncertainty over Brexit. The FMCG market is reliant on strong economic growth – the more money consumers have to spend, the more retailers benefit.”
Michelle Wilson, business manager at Danske Bank, said: “Ricky and Vicky are ambitious owners with a wealth of experience in the retail market. By closely following consumer trends, they have adapted their offering to meet the demands of an ever-changing sector.
“We are proud to support locally-owned family businesses and provide them with the right resources to grow.”


Gopichand Arya Mahila College,Abohar is a premier Education Institute serving the cause of women education . The college is affiliated to Panjab university ,Chandigarh and is under DAV College Managing Committee , New Delhi. Ever since its inception in 1972, it has taken a tremendous leap in academic, cultural and sports fields and has carved an enviable niche for itself among the women colleges of this area . The college is successfully running various degree, diploma and post graduate courses. The college is actively involved in social engineering to neutralize serious social imbalance and distortions. Our motto is to prepare our students to face the challenges of life with dignity and confidence, preserving the social ethos, vedic doctrines and our cultural heritage. Our institute adopts the approach of building multi-dimensional personalities having deep-rooted faith in philosophy of Arya Samaj clubbed with the latest trends of education. The responsibility of building future of the nation is carried out with all the honesty and integrity.
Gopichand Arya Mahila College is a premier multi-faculty Post Graduate Institute. It was established by DAV College Managing Committee, New Delhi with the initial donation of Rs. 2.5 Lacs from Seth Gopichand Ahuja Charitable Trust in May 1972.
Ms. Kamla Khanna, the founder Principal, placed the institution on a sound footing administratively as well as academically.
The institution made progress under the stewardship of Principal Kanta Sarin, Mrs. K. Sachdev, Dr. Manju Mehta and Dr. Achla Kant.
At present, Dr. Rekha Sood Handa with her unflagging Commitment, missionary zeal and visionary approach has placed the institution on the road of journey towards excellence and is providing every possible opportunity to the students to develop their latent potentialities.
The institution is serving the cause of women education not only in Abohar city but also its surrounding villages and towns of Punjab, Haryana and Rajasthan. Since its inception in 1972, it has taken a tremendous leap in the academic, cultural and sports fields and has carved an enviable niche for itself among the women colleges of the city.
The college is successfully running various degree, diploma and post graduate courses. The college can rightfully claim of its achievements at various socio-educational fronts with many ‘Firsts’ to its credit.
Today the college has a spacious walled campus.
It has sufficient classrooms, well-equipped Home Science laboratories, Fine Arts block,
Computer Block, well-stocked Library with stack room and A.C. Reading Hall, Conference hall, Common room,
Auditorium, Canteen, Play fields, Open Air Theatre and a Hostel with well-equipped common room with exclusive lawns for
the residents. In hostel there is provision for A.C. rooms also. The New IT & PG Block, Indoor Facilities stadium and a
swimming pool is under construction.
Copyright © 2018 All Rights Reserved
Marketed by:- SHIYA SUKHIJA, KAJAL JAKHAR


#1 Assignment - Assembly
Part 1
Clone the repository https://github.com/os-course/iustfall20. Try to load and boot the examples as we have done in class. Note that use VirtualBox as a Virtual Machine tool for running the generated ISO file. Make sure all the examples successfully executed.
$ clone https://github.com/os-course/iustfall20
$ cd iustfall20/01_bootloader
$ chmod +x generate_iso.sh
$ ./generate_iso.sh file_name.asm
Part 2
Add a function to move the cursor of the screen to the head of the next line. Name the function
print_newline
You might use 2 special characters. Their hex value is 0x0A and 0x0D.
Part 3
Add a new label to the printer.asm
file named print_hex
.
This will be used as the function to print hexadecimal numbers in the main file.
Note that you might need to use and
,
shr
,
shl
, and dec
CPU instructions.
To test your implementation, put a hexadecimal number into an address of memory.
Then print_hex
the number in that address.
; Example of a test for the print_hex function
mov dx, 0xA91F
call print_hex
; The result should be 0xA91F
Deadline
- Friday 9th Oct. 23:00
Submission
Submit just a zip file in LMS. The file should be named as
[9752xxxx.zip
].
For example 97521234.zip
.


Seven Years In Tibet A Film Review By Michael Redman Copyright 1997 By Michael Redman
***1/2 (out of ****)
Hollywood has a history of concentrating on a contemporary political topic for a short time then rapidly moving on. This year's cause celebre is Tibetan freedom and as usual, the tale of an exotic people is seen through the eyes of a westerner. "Seven Years" will be followed in short order by Martin Scorsese's "Kundun", another film about the Dalai Lama.
In 1939, given the opportunity to climb Nanga Parbat in the Indian Himalayas, obnoxious self-centered Austrian Heinrich Harrer (Brad Pitt) jumps at the chance and abandons his pregnant wife. The expedition doesn't go well for the Olympic medal-winner when the team fails to reach the summit. Then, before they can try again, World War II breaks out and they are rounded up by British troops and sent to a POW camp near the Indian-Tibetan border.
After several abortive escape attempts by the lone wolf, he reluctantly joins a group plan and finds himself outside the barbed wire. Because he is obviously superior to the rest of the escapees, he heads out on his own. He hopes to make his way through Tibet to China, meet up with the Japanese troops and eventually get back home.
Days later, fellow mountain-climbing prisoner Peter Aufschnaiter (David Thewlis) chances upon Harrer as the arrogant adventure is starving and vomiting in the wilderness. The two form an uneasy alliance and continue their trek towards the mysterious nation.
Sneaking into the country that forbids entry to all outsiders, the two eventually get to the holy city of Lhasa. Harrer attracts the attention of the young Dalai Lama who grants him an audience. The teenage spiritual leader is interested in western ways and asks him to teach knowledge of the distant world. Forging a friendship, the two learn from each other until Harrer is forced to flee the country during the Chinese onslaught of the early fifties.
The film is exquisitely beautiful and epic in scope. The Argentinean Andes serve as a stand-in for the Himalayas because the Chinese government refused permission to film in Tibet, but are no less majestic. The mystical city is visually captivating. The esoteric costumes and setting are beyond reproach.
According to Mike Leonard's interview in the "Herald Times" with Thubten J. Norbu, the Dalai Lama's older brother, the Winter Palace is "extraordinarily realistic." Bloomington resident and retired IU professor Norbu and another brother were responsible for bringing Harrer to the attention of the Dalai Lama. Unfortunately this aspect of the story is not in the film as Norbu refused to allow his depiction when the producers would not provide him with any assurance of how he would be presented. Their sister plays the holy mother in a cameo appearance.
Pitt is satisfactory in his role, but never completely engages the audience. The most interesting aspect of the film -- his conversion to selflessness -- is glossed over and we are expected to just accept it. Every story depends upon change in the lead character and more time spent with that transformation would help to flesh out the tale. The recent revelation that Harrer was not only Nazi but a member of the SS before his imprisonment would have made his conversion even more dramatic, but that detail was withheld from the filmmakers.
Although Harrer's saga is enticing, director Jean-Jacques Annaud ("The Bear", "Quest For Fire") could have eliminated some of the adventurer's solo deeds and looked more at the relationship between him and the Dalai Lama.
Much more captivating is Jamyang Jamtsho Wangchuk as the 14 year old Dalai Lama. Successfully combining the curiosity and energy of an adolescent boy with the ancient wisdom of the reincarnation the Buddhist God of Compassion, the young actor is easily the star of the film. The decision to humanize the Dalai Lama rather than portray him as somber holy man is a wise one. Anyone who has been in his presence can attest to his sense of humor and easy-going ways.
The film is guilty of some political heavy-handedness especially in the scene of Chinese generals stomping through a sand mandala. Moments before, the spectacle as soldiers in staid uniforms march through the flowing robes of horn-blowing Buddhist monks is subtle and much more effective in demonstrating the differences between the invaders and the peaceful Tibetans.
It is interesting to observe the ebb and flow of popular icons. During the sixties, Chairman Mao was upheld as a hero by the American culturally elite. Now he is the villain as attention is focused on the Chinese occupation of Tibet. There is no excuse for the murder of over a million Tibetans and the destruction of 90% of the monasteries by the Chinese and it is obvious that Tibet deserves its independence. However history has taught us that over-simplification results in inaccuracies and very un-Buddhist-like polarities. One would hope that the champions of Tibetan freedom don't make this mistake.
The film accomplishes many rare feats. It is majestic, entertaining and at the same time will undoubtedly aid in the mobilization of western sentiment for the liberation of Tibet. With the notoriously short attention span of the popular media, we can only hope that the focus lasts long enough to manifest change.
(Michael Redman, veteran of over 22 years of these film columns, is busy bringing in his plants in the face of freezing weather. At least he is busy thinking about it<EMAIL_ADDRESS>is the eaddress to send clever comments.)
[This appeared in the 10/15/97 "Bloomington Voice", Bloomington, Indiana. Michael Redman can be reached at<EMAIL_ADDRESS>] --<EMAIL_ADDRESS>The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


Brining interactive stories about brave people into the classroom
Swedish Heroes in School is an interactive online seminar aiming at shaping the next-generation of youth heroes.
My work included delivering a video based learning platform written in PHP (Codeigniter), front-end in JQuery, SASS and deployment to Heroku.


Preparation time
1-2 hours
Cooking time
10 to 30 mins
Serves
Serves 4
For the prawns, coat the raw prawns with the chilli and garlic sauce in a shallow non-reactive dish. Cover and leave to marinate for up to an hour.
Heat the olive oil in a griddle pan until smoking. Add the prawns and cook until well coloured (about three minutes each side).
Set the prawns aside in a warm place while you make the salad and dressing.
For the herb salad, combine the ginger and garlic with the olive oil in a pan and gently heat. Once warmed through, remove from the heat and leave to infuse.
Mix the balsamic vinegar, infused olive oil and the caster sugar in a small bowl to make a smooth emulsion.
Lightly toss all the herbs together in a separate bowl. Add the dressing, a little at a time, until the herbs are coated.
To serve, arrange a little of the salad on each of four plates and serve with the prawns straightaway.


editable-table Importing Data
The following import options work with both
editable-table-display
and
editable-table
modes.
Using a CSV
Using an Array
Using HTML (progressive enhancement method)
The Money Game: Player earnings by round..
Round
Player 1
Player 2
Round 1
-100.00
10.00
Round 2
1823.01
1211.80
Round 3
1100.50
4873.99
Round 4
3800.75
921.00
Total
$6624.26
$7016.79


Our Impressive Portfolio
With Competitive Prices
Nothing is Impossible!
Chief Executive Officer
Worldwide Engineer
Elevator etiquette in 9 easy steps for returning to the workplace
Global Smart and Connected Elevators and Elevators Control System Market with (Covid-19) Impact Analysis
How to stay healthy when you eventually return to the office post-pandemic
How coronavirus pandemic will impact energy efficient elevators market
HOMETOWN HELPERS: Elevator tech stretched as he helps keep VA hospital in Brooklyn running
Coronavirus: Is It Safe To Ride an Elevator? Experts Explained Safety Mechanics


Spinner with expanding and contracting motion
Spinner based on a fixed arc
Arc with controllable size
Contract
Expand


#include <GrAfterEffect.h>
Classes | |
| struct | SUserParam |
| vec4 parameter. | |
| struct | SUserSampler |
| sampler parameter. | |
Public Member Functions | |
| GrAfterEffect (const tstring &name, URef< GrShader > shader, const MVec3 &position, float width, float queryWidth) | |
| class header. | |
| ~GrAfterEffect () | |
| void | SetPosition (const MVec3 &position) |
| void | SetWidth (float width) |
| void | SetQueryWidth (float queryWidth) |
| const MVec3 & | GetPosition () const |
| float | GetWidth () const |
| float | GetQueryWidth () const |
| unsigned int | InitParam (const char *name, const URef< UExpr > *values) |
| parameter management. | |
| unsigned int | GetParamHandle (const char *name) const |
| void | SetParamExprs (unsigned int handle, const URef< UExpr > *values) |
| void | GetParamExprs (unsigned int handle, URef< UExpr > *values) |
| 4 exprs. | |
| unsigned int | InitSampler (const char *name, const GrTextureStage &textureStage) |
| 4 exprs. | |
| unsigned int | GetSamplerHandle (const char *name) const |
| const GrTextureStage & | GetSamplerStage (unsigned int handle) const |
| void | SetSamplerStage (unsigned int handle, const GrTextureStage &stage) |
| void | Update () |
| updates the effect. | |
| void | ApplyShaderParams () |
| state management. | |
| void | ApplySamplerStates () |
Static Public Attributes | |
| static const unsigned int | kMaxSamplerCount = 8 |
Definition at line 24 of file GrAfterEffect.h.
| GrAfterEffect::GrAfterEffect | ( | const tstring & | name, | |
| URef< GrShader > | shader, | |||
| const MVec3 & | position, | |||
| float | width, | |||
| float | queryWidth | |||
| ) |
class header.
File: GrAfterEffect.cpp Author: Kevin Bray Created: 01-04-04 Copyright © 2004 Bootstrap Studios. All rights reserved. project includes. class GrAfterEffect ctor & dtor
Definition at line 27 of file GrAfterEffect.cpp.
| GrAfterEffect::~GrAfterEffect | ( | ) |
Definition at line 39 of file GrAfterEffect.cpp.
| void GrAfterEffect::ApplySamplerStates | ( | ) |
Definition at line 219 of file GrAfterEffect.cpp.
| void GrAfterEffect::ApplyShaderParams | ( | ) |
4 exprs.
ensure that the parameters passed in are valid.
get the expressions.
Definition at line 115 of file GrAfterEffect.cpp.
| unsigned int GrAfterEffect::GetParamHandle | ( | const char * | name | ) | const |
ensure that a valid name was passed in.
find the parameter with the name specified.
if the parameter wasn't found, simply return ~0.
Definition at line 80 of file GrAfterEffect.cpp.
| const MVec3& GrAfterEffect::GetPosition | ( | ) | const [inline] |
Definition at line 38 of file GrAfterEffect.h.
| float GrAfterEffect::GetQueryWidth | ( | ) | const [inline] |
Definition at line 40 of file GrAfterEffect.h.
| unsigned int GrAfterEffect::GetSamplerHandle | ( | const char * | name | ) | const |
ensure that a valid name was passed in.
find the sampler with the name specified.
if the sampler wasn't found, simply return ~0.
Definition at line 155 of file GrAfterEffect.cpp.
| const GrTextureStage & GrAfterEffect::GetSamplerStage | ( | unsigned int | handle | ) | const |
| float GrAfterEffect::GetWidth | ( | ) | const [inline] |
Definition at line 39 of file GrAfterEffect.h.
parameter management.
public methods
ensure that incoming values are valid.
determine a good unique ID for the param.
if the param doesn't exist, simply return ~0.
initialize param data.
the param exists, so return the handle.
Definition at line 51 of file GrAfterEffect.cpp.
| unsigned int GrAfterEffect::InitSampler | ( | const char * | name, | |
| const GrTextureStage & | textureStage | |||
| ) |
4 exprs.
sampler management.
ensure that the parameters passed in are valid.
add a new sampler.
try to bind the user sampler. If we can't, then simply return ~0.
fill out the sampler data.
set the sampler's name and texture stage data.
return the handle.
Definition at line 128 of file GrAfterEffect.cpp.
ensure that the parameters passed in are valid.
set the expressions.
Definition at line 99 of file GrAfterEffect.cpp.
| void GrAfterEffect::SetPosition | ( | const MVec3 & | position | ) | [inline] |
Definition at line 34 of file GrAfterEffect.h.
| void GrAfterEffect::SetQueryWidth | ( | float | queryWidth | ) | [inline] |
Definition at line 36 of file GrAfterEffect.h.
| void GrAfterEffect::SetSamplerStage | ( | unsigned int | handle, | |
| const GrTextureStage & | stage | |||
| ) |
| void GrAfterEffect::SetWidth | ( | float | width | ) | [inline] |
Definition at line 35 of file GrAfterEffect.h.
| void GrAfterEffect::Update | ( | ) |
updates the effect.
spin through parameters and update.
get the current parameter and then evaluate and cache it's expressions.
Definition at line 192 of file GrAfterEffect.cpp.
const unsigned int GrAfterEffect::kMaxSamplerCount = 8 [static] |
units 0..7 are for use by the effect. Units after that are reserved by the system.
Definition at line 29 of file GrAfterEffect.h.


There has always been a clear separation between practical programming languages and mathematical programming constructs. Even languages such as ML and Scheme, though rooted in mathematics, have unnecessary or impure extensions for convenience and performance. While these extensions improve the language in some ways, they also make it more complex; programs become difficult to reason about for humans and computers alike, and maintaining a compliant, feature-rich software stack becomes impossible.
Simplicity is necessary to reason about a programming language; and when writing simple programs, most programming languages are. It is when programs get more complex, be it for performance or for abstraction, that simplicity is discarded; complex extensions are used, edge cases are abused, and reasoning about a program becomes impossible.
M is not just a simple programming language — mathematical systems like Turing Machines and the \lambda-Calculus are much simpler than M is. M is a simple programming language which can express the extensions provided by practical programming languages without the additional complexity.
To do this, M exhibits the following properties:
All functions are pure.
All definitions are unordered.
Any two expressions can be made equivalent with a macro.
\begin{aligned} &\forall\mathbb{C} &:&\;\mathbb{C}\supseteq\texttt{(special + newline + whitespace)}\land\texttt{null}\notin\mathbb{C}\\ &\texttt{character} &=&\;\mathbb{C}\\ &\texttt{special} &=&\;\{\texttt{`;'},\texttt{`"'},\texttt{`('},\texttt{`)'}\}\\ &\texttt{comment} &=&\;\texttt{`;'}\quad\texttt{(character - newline)*}\quad\texttt{newline}\\ &\texttt{symbol} &=&\;\texttt{(character - special - whitespace)*}\\ & &|&\;\texttt{`"'}\quad\texttt{character*}\quad\texttt{`"'}\\ & &|&\;\texttt{`""'}\quad\texttt{character*}\quad\texttt{`""'}\\ &\texttt{expression}&=&\;\texttt{symbol}\\ & &|&\;\texttt{`('}\quad\texttt{expression*}\quad\texttt{`)'}\\ &\texttt{program} &=&\;\texttt{expression*} \end{aligned}
M can use any character set which encodes the four special characters, a newline character, and a whitespace character. To allow for simple source termination, the null character is not allowed in M code. For consistency, all M code samples in this specification use the ASCII character set.
The four special characters are reserved for use in non-symbol syntax. This is done to ensure that symbols do not include any characters which are meant be used in other syntactic constructs. For example, if the special characters were not reserved, the expression (symbol)
would be parsed as ("symbol)"
rather than ("symbol")
.
Comments in M begin with a semicolon and last until the end of the line. They are ignored and discarded like whitespace and newlines, as they are only intended to be used for explaining code. M does not provide multiline comments, as comments are not meant to be used for documentation.
Symbols in M have two forms, inline and literal. Inline symbols are strings terminated by whitespace or a special symbol, and should be used by default. Literal symbols are strings surrounded by one or two quotes, and should only be used when a symbol is impossible to represent using inline symbols (for example, the symbols "()"
and """"
).
Expressions in M have two forms, symbols and lists. Symbol expressions are symbols as defined in section 2.4. Lists expressions are lists of expressions surrounded by matching parentheses.
M programs are lists of expressions. Note that there is no terminator specified for M programs, nor a separator for expressions, so M programs are not restricted to a single file or a null-terminated string.
\begin{aligned} &\frac{\Gamma(x)=(v,m)}{\langle x,\Gamma\rangle\Downarrow\langle v,\Gamma\rangle} &\frac{\langle e,\Gamma\rangle\Downarrow\langle v,\Gamma\rangle}{\langle\texttt{(macro x e)},\Gamma\rangle\Downarrow\langle v,\Gamma(x) = (v,\top)\rangle}&\\ &\frac{\Gamma}{\texttt{(fn x e)}\Downarrow\langle\lambda x.e,\Gamma\rangle} &\frac{\Gamma(m)=(f,\top)\quad\langle f(\Gamma,e),\Gamma\rangle\Downarrow\langle v,\Gamma\rangle}{\langle\texttt{(m e)},\Gamma\rangle\Downarrow\langle v,\Gamma\rangle}&\\ &\frac{\langle e,\Gamma\rangle\Downarrow\langle v,\Gamma\rangle}{\langle\texttt{(def x e)},\Gamma\rangle\Downarrow\langle v,\Gamma(x) = (v,\bot)\rangle} &\frac{\langle f,\Gamma\rangle\Downarrow\langle\lambda x.e,\Gamma\rangle\quad\langle a,\Gamma\rangle\Downarrow\langle i,\Gamma\rangle}{\langle\texttt{(f a)},\Gamma\rangle\Downarrow\langle e[x/i],\Gamma\rangle}& \end{aligned}
Symbol expressions are expressions of the form name
. They always evaluate to the value
of the def expression that defines name
. If name
is not defined, they evaluate to \bot instead.
Function expressions are expressions of the form (fn name value)
. They evaluate to a function \lambdaname
.value
. When applied, they perform the substitution value
[name
/argument
].
Def expressions are expressions of the form (def name value)
. They state that all symbols name
evaluate to value
, and evaluate to the the value
they define. Multiple def expressions with the same name
are invalid.
Macro expressions are expressions of the form (macro name value)
. They state that all symbols name
evaluate to value
, and evaluate to the value of name
. When applied, macros evaluate name
and apply it to the environment and the expression of argument
, then evaluate the result.
Apply expressions are expressions of the form (value argument)
. If value
is a function, it performs application as described in section 3.2.


These commands define initialized data:
DB 8-bit constants and strings DC strings; every last character of a string will have bit 7 set DD 32-bit constants DT 24-bit constants DW 16-bit constants DZ strings; each string will be zero terminated BYTE 8-bit constants and strings WORD 16-bit constants DWORD 32-bit constants DEFB 8-bit constants and strings DEFM 8-bit constants and strings DEFW 16-bit constants DEFD 32-bit constants
The syntax is:
[label] [[repeatcount]] COMMAND [expr][,expr]... [comment]
where expr is an expression, constant or label where constants are allowed, or a string where strings are allowed.
Examples:
db 0x55 db 'a'-2, "hoppa!" db 2*7 dc "kip","abc" dw "a" dz "kip","abc"
To define a space you can use DS (or BLOCK or |DEFS, there is no difference). The first argument specifies the number of bytes to fill with the second argument. When the second argument is omitted, zero is used:
ds 10 ; byte 0,0,0,0,0,0,0,0,0,0 ds 5,3 ; byte 3,3,3,3,3 block 2 ; byte 0,0
The following commands add an offset to the specified bytes or string: ABYTE, ABYTEC and ABYTEZ. ABYTEC and ABYTEZ work similar to DC and DZ, and allow only strings. The syntax is:
[label] [repeatcount] ABYTE [offset] [expr][,expr]... [comment]
Examples:
abyte 1 0,1,2,3 ; byte 1,2,3,4 abytez 3 "abc" ; byte "def",0
It is also possible to define a mapping table with ASCMAP and use that to translate the strings using the ASC command. This way it is possible to map any character to any other. The first argument to ASCMAP defines a character or a character range; the second argument defines an expression telling how to translate the characters. In this expression $ specifies the character currently being translated.
ascmap 'a'..'z', $+1 ; a=>b, b=>c, c=>d, ... ascmap 'a'..'z', $+'A'-'a' ; make uppercase ascmap '*', 'a' ; translate * to a
Another, less flexible, way is to specify the range of characters to translate, the first character to translate to and optionally a step increment. The increment is one if the step increment is omitted.
ascmap '0'..'9', => 0 ; 48 ('0') =>0, 49=>1, ... ascmap 'a'..'z', => 'A' ; make uppercase ascmap 'a'..'z', => 65,2 ; a=>A, b=>C, c=>E, ... ascmap 'a'..'z', => 'z',-1 ; a=>z, b=>y, c=>x, d=>w, ...
Instead of ascmap 0..255, $ you can use ASCMAP.RESET to get the default 1:1 mapping back. You can use ASCMAP.CLEAR to get all characters translate to 0.
To use the defined mapping table, use the ASC command instead of DB:
asc "de kip",13,"a"+12
Likewise ASCC and ASCZ work like DC and DZ:
ascz "de kip" ; only strings allowed
Structures can be used to define data structures in memory more easily. A structure is a group of fields that can have different sizes. When you declare a structure, Sjasm will create labels with the offsets of the fields, so you could use a structure instead of a storage map. The name of the structure will be used to create a label with the total size of the structure.
You declare a structure with the STRUCT and the ENDSTRUCT commands:
STRUCT name [, initial offset, alignment] <fields> ENDSTRUCT
You could use ENDS instead of ENDSTRUCT.
When you declare the structure in a module, the module name will be part of the name, just like labels. If you do not want this, put an @ before the name.
The initial offset and the alignment are both optional. Normally this offset is 0, but it can be set to any value. You can specify a default alignment that is used for all the fields.
The syntax for defining the structure fields is as follows:
label type operands
All fields are optional, and not all types have operands. Sjasm recognises the following types:
DB [<defaultvalue>] ; define a 1 byte field DW [<defaultvalue>] ; define a 2 byte field DT [<defaultvalue>] ; define a 3 byte field DD [<defaultvalue>] ; define a 4 byte field DS length [,<defaultvalue>] ; define a field of the given length ## alignment value ; align the following field # length ; define a field of the given length
BYTE can be used instead of DB, WORD instead of DW, DWORD instead of DD and ALIGN instead of ##.
The default value is used when no initialisation value is given when the structure is defined. The default of a DS field can contain more than one expression or string, as long the length of the field is not exceeded.
The default alignment and the alignment field only align the structure offsets, and not the actual addresses where the structure is used.
Some examples of structure declarations:
struct scolor red db 4 green db 5 blue db 6 endstruct
This will create the following labels:
scolor.red ; 0 offsets scolor.green ; 1 scolor.blue ; 2 scolor ; 3 length of the structure
In the following example we will use a default alignment:
struct spul,,2 x db 0 y db 0 name ds 8, "noname",1 endstruct
This will create the following labels:
spul.x ; 0 offsets spul.y ; 2 spul.name ; 4 spul ; 12 length
When a structure is declared, you can define labels with it:
color1 scolor color2 scolor 12,,14
This is identical to:
color1 .red db 4 .green db 5 .blue db 6 color2 .red db 12 .green db 5 .blue db 14
As you see, the default values are overwritten when you specify new values. Fields declared as # will always be initialized with zero and cannot be overwritten.
When you use a structure without a preceding label, no labels are generated, but of course, the data will be defined as expected:
spul
wil generate the following data:
db 0 ; x ds 1 ; align db 0 ; y ds 1 ; align db "noname",1 ; name ds 1 ; padding


Peter Baynham's panel show appearances
Peter Baynham has appeared in 18 episodes between 1998-2004. Peter Baynham on IMDB. Peter Baynham on Wikipedia.
- 2004-09-08 / The 99p Challenge
- 2004-08-25 / The 99p Challenge
- 2003-03-04 / The 99p Challenge
- 2003-02-25 / The 99p Challenge
- 2001-08-16 / The 99p Challenge
- 2001-07-26 / The 99p Challenge
- 2000-07-27 / The 99p Challenge
- 2000-07-20 / The 99p Challenge
- 2000-07-13 / The 99p Challenge
- 2000-07-06 / The 99p Challenge
- 2000-06-29 / The 99p Challenge
- 2000-06-22 / The 99p Challenge
- 1998-10-27 / The 99p Challenge
- 1998-10-20 / The 99p Challenge
- 1998-10-13 / The 99p Challenge
- 1998-10-06 / The 99p Challenge
- 1998-09-29 / The 99p Challenge
- 1998-09-22 / The 99p Challenge