ONLINE HELP
 WINDEVWEBDEV AND WINDEV MOBILE

This content has been translated automatically.  Click here  to view the French version.
  • Overview
  • SELECT
  • SELECT
  • Special case: Multi-file SELECT
  • Special cases: SELECT without FROM or SELECT using a virtual table
  • Running a stored procedure
  • INSERT
  • INSERT
  • INSERT with selection
  • UPDATE
  • DELETE
  • CREATE TABLE
  • Available types
  • CREATE TABLE AS
  • DROP TABLE
  • ALTER TABLE
  • CREATE INDEX
  • DROP INDEX
  • GRANT
  • REVOKE
  • CREATE VIEW
  • DROP VIEW
  • CREATE MATERIALIZED VIEW
  • DROP MATERIALIZED VIEW
  • REFRESH MATERIALIZED VIEW
  • OPTIMIZE TABLE
WINDEV
WindowsLinuxUniversal Windows 10 AppJavaReports and QueriesUser code (UMC)
WEBDEV
WindowsLinuxPHPWEBDEV - Browser code
WINDEV Mobile
AndroidAndroid Widget iPhone/iPadIOS WidgetApple WatchMac CatalystUniversal Windows 10 App
Others
Stored procedures
Overview
The main SQL statements allowed in the SQL queries managed by WINDEV and WEBDEV are as follows:
For more details, see the SQL documentation.
To discover all the SQL commands (functions, clauses, operators, etc.) that can be used in an SQL query managed by HFSQL, see Commands that can be used in an SQL query managed by HFSQL.
Remarks:
  • These statements can be used:
    • in the SQL code of queries created in the query editor. Then, these queries will be run by HExecuteQuery.
    • in the SQL code of queries run by HExecuteSQLQuery.
  • The " character can be used as an identifier delimiter in SQL queries as specified by the SQL92 standard.

Important

The query editor cannot be used to create queries in SQL using the INSERT, UPDATE or DELETE commands.
SELECT

SELECT

The SELECT statement is used to find records in one or more files.
Format:
SELECT [ALL/DISTINCT] NomDesRubriques AS NomDesRubriquesUtilisé
FROM NomDesFichiers AS NomDesFichiersUtilisé
[WHERE JointuresFichiers [AND] ConditionsSélection]
[GROUP BY NomDesRubriques [HAVING Condition]]
[ORDER BY NomDesRubriques]
Example: The following SQL code is used to display the total sum of orders per customer living in Paris. The result will be grouped by customer name, by zip code and by city. Only the customers with a total amount of orders exceeding 3000 Dollars will be considered. The result will be sorted by total order amount in ascending order:
SELECT CLIENT.NomClient AS NomClient,
CLIENT.CodePostal AS CodePostal,
CLIENT.Ville AS Ville,
SUM(COMMANDE.TotalTTC) AS TotalTTC
FROM CLIENT AS CLI, COMMANDE AS COM
WHERE CLI.NumClient = COM.NumClient
AND CodePostal LIKE '21%'
GROUP BY NomClient, CodePostal, Ville
HAVING TotalTTC > 3000
ORDER BY TotalTTC ASC
Remarks:
  • The DISTINCT keyword deletes the duplicate records from the query result.
  • For more details on selection conditions, see Selection conditions in an SQL query.
  • A Select query can be created in the query editor. For more details, see Creating a select query.
  • In the syntax of the SELECT statement, the NameOfItems parameter can contain one or more selection conditions. For example:
    SELECT NomClient, Age>18 AS Majeur FROM Client
  • The SELECT statement can be used as a scalar expression, which means that the SELECT statement can be used instead a value or a column in the statements such as INSERT, UPDATE, SELECT, etc.
    For example:
    INSERT INTO TABLE (nom, valeur) VALUES ( 'Rome a Rick',
    (SELECT MAX(nombre) FROM table2) )

    UPDATE client SET cattc = (SELECT SUM(facture.total_ttc)
    FROM facture WHERE facture.reference=client.reference)

    SELECT NomDesRubriques AS NomDesRubriquesUtilisé,
    (SELECT COUNT(*) FROM Table2 WHERE Rub=NomDesRubriquesUtilisé)
    FROM Table1
Remark: The SELECT query used as scalar expression must return a result on a single column and a single row.

Special case: Multi-file SELECT

The SELECT statement can also be used to select in a multi-file query all the columns of a data file via the following syntax:
SELECT Ligne_Commande.*,Commande.NumCommande, ...
FROM Commande, Ligne_Commande
This syntax is very useful for joins and avoids entering the name of each item, which is a long and tedious operation.

Special cases: SELECT without FROM or SELECT using a virtual table

The SELECT statement can also be used to run queries that do not apply to tables:
  • by using the syntax without "FROM" clause.
    Example:
    SELECT TO_CHAR(12,'RN')
  • by using the virtual DUAL table. This virtual table contains a single item (named "dummy") and a single record.
    Example:
    SELECT CURRENT_DATE FROM DUAL

Running a stored procedure

The SELECT statement can also be used to run stored procedures directly.
The following syntax is used to run the stored procedure:
SELECT NomDeLaProcédureStockée(ParamètreNumérique,'ParamètreChaîne',....) [FROM DUAL]
Remarks:
  • The stored procedure must be a stored function: it must always return a value. Otherwise, a runtime error will occur.
  • If the name of the stored procedure is identical to the name of a function or procedure found in the project, its name must be prefixed by the name of the set of stored procedures.
  • The result of a stored procedure can also be one of the parameters of a SELECT query.
INSERT

INSERT

The INSERT statement is used to add :
  • one record to a data file.
  • multiple records to a data file.
It uses the following format:
INSERT INTO NomDuFichier (NomDesRubriques)
VALUES (ValeurRubriques ajout 1), (ValeurRubriques ajout 2), ... , (ValeurRubriques ajout N)
Examples: The following SQL code lets you insert:
  • a new customer in the CUSTOMER file (the complete customer information is provided).
    INSERT INTO CLIENT
    VALUES (77, 'Martin', 'Julie', '1 rue Giono',...
    '69000', 'Lyon')
  • a new customer in the CUSTOMER file: only the customer's number, last name and first name are provided:
    INSERT INTO CLIENT (NumClient, NomClient, PrénomClient)
    VALUES (77, 'Martin', 'Julie')
  • 3 products in a PRODUCT file:
    INSERT INTO PRODUIT
    VALUES ('Ref01', 'Eau minérale'), ('Ref02', 'Beurre'), ('Ref03', 'Eponge')
Remarks:
  • An Insert query can be created in the query editor. For more details, see Creating an insert query.
  • To get the value of the automatic identifier in an INSERT query on HFSQL files (query run by HExecuteSQLQuery, HExecuteQuery or SQLExec), use the SQL.IDAuto variable.
  • The INSERT command cannot be used to copy data from one HFSQL file to another.
  • The H.NbRecModificationQuery variable is used to find out the number of inserted records.

INSERT with selection

The INSERT statement can also be used to insert a selection of records made by a SELECT query. Its use format becomes:
INSERT INTO NomDuFichier [(NomDesRubriques)] SELECT ...
Remarks:
  • The number of items to insert must be identical to the number of values returned by the SELECT query.
  • All the files items are taken into account if no item is specified in the INSERT statement.
  • This type of query cannot be created in the query editor.
UPDATE
The UPDATE statement is used to update the records in a file. It uses the following format:
UPDATE NomDuFichier
SET NomDeLaRubrique = Expression
[WHERE Conditions]

Examples:
  • The following SQL code is used to modify the title and the name of the customer named Montgomery:
    UPDATE CLIENT
    SET Civilité = 'Mme',
    NomClient = 'Darwin'
    WHERE NomClient = 'Montgomery'
  • The following SQL code modifies the client name in the Customer file. For records with the same ID, the name of the client in the Customer file is replaced with the name in the Customer_Svg file:
    UPDATE CLIENT, CLIENT_SVG
    SET CLIENT.Nom = CLIENT_SVG.Nom
    WHERE CLIENT.ID = CLIENT_SVG.ID
Notes about the use of the UPDATE statement in WINDEV:
  • The WHERE clause accepts the sub-selections (by SELECT), if the sub-selection is relative to another file.
  • The SET expression accepts no sub-selection (no SELECT command just after SET). Similarly, no join can be performed in the UPDATE section of the query.
    The following query is accepted:
    UPDATE ARTICLE SET PrixVente = 1000
    WHERE RefArticle IN (SELECT COMMANDE.RefArticle FROM COMMANDE)
    -- Sous-requête dans le WHERE: OK

    The following query is not accepted:
    UPDATE Article
    SET PrixVente = (SELECT AVG(COMMANDE.PrixUnit) FROM COMMANDE
    -- Sous-requête dans le SET: NON VALIDE
    WHERE COMMANDE.RefArticle = ARTICLE.RefArticle
    GROUP BY COMMANDE.RefArticle)
    WHERE RefArticle IN (SELECT COMMANDE.RefArticle FROM COMMANDE)

    Similarly, the following query is not accepted:
    UPDATE Article INNER JOIN PRODUIT ON (Article.IdArticle = Produit.IdProduit)
    -- Jointure dans le UPDATE: NON VALIDE
    SET Article.PrixVente = Produit.PrixVente
    WHERE RefArticle IN (SELECT COMMANDE.RefArticle FROM COMMANDE)
  • An Update query can be directly created in the query editor. For more details, see Creating an update query.
  • The quote is used as delimiter for the strings in the SQL queries. If the value to write must contain a quote, this value must be preceded by the backslash character ('\'). For example:
    UPDATE CLIENT
    SET Adresse = 'Rue de l\'écluse'
    WHERE NomClient = 'Montgomery'
  • The H.NbRecModificationQuery variable is used to find out the number of updated records.
DELETE
The DELETE statement is used to delete records from a file. It uses the following format:
DELETE FROM NomDuFichier
WHERE Conditions
Example: The following SQL code is used to delete the customers born between 01/01/1971 and 01/01/1975:
DELETE FROM CLIENT
WHERE CLIENT.DateNaissance BETWEEN '19710101' AND '19750101'
Remark: A Delete query can be created in the query editor. For more details, see Creating a delete query.
CREATE TABLE
The CREATE TABLE statement is used to describe and create a table. It uses the following format:
CREATE TABLE NomTable
(
Description de la rubrique1,
Description de la rubrique2,
...
Description Index1,
Description Index2,
...
)
In this syntax:
  • the "Description of item" parameters correspond to:
    NomRubrique type  [DEFAULT 'valeur' | DEFAULT NULL] [NOT NULL]
      [UNIQUE] [PRIMARY KEY]  [AUTO_INCREMENT] [COMMENT 'libellé'] [COLLATE 'langue']
    where:
    • ItemName: name that will be given to the item.
    • Type: Type of item (chosen among the available types presented below).
    • DEFAULT: defines the default value of the item.
    • NOT NULL: prevents from assigning the NULL value to the item.
    • UNIQUE: defines an item with or without duplicates.
    • PRIMARY KEY: defines a primary key.
    • AUTO_INCREMENT: defines an automatic identifier for items of type Integer.
    • COMMENT: defines the caption of the item.
    • COLLATE: defines the language for sorting the item (for example: 'EN_US').
  • the "Description of index" parameters correspond to:
    INDEX  NomIndex ( rub1 [ASC | DESC] [CI] [AI] [PI], rub2 ... ) [UNIQUE]
    where:
    • ASC and DESC: allows you to define the sorting direction.
    • CI: specifies that the case is ignored.
    • AI: specifies that accented characters are ignored.
    • PI: specifies that the punctuation is ignored.
Remark: This type of query cannot be created with the wizard found in the query editor.

Available types

The available types are:
CHARACTERCharacter string
CHARACTER(Size)String on size
VARCHAR(Size)String on size
CHARACTER VARYING(Size)String on size
CHAR VARYING(Size)String on size
NVARCHAR(Size)Unicode string on size
VARCHAR(Size) BINARYBinary string on size
BINARY(Size)Binary string on size
VARBINARY(Size)Binary string on size
BLOBBinary memo
CLOBText memo
TEXTText memo
NCLOBUnicode memo
NTEXTUnicode memo
NUMBER(Precision)Integer
NUMBER(Precision, scale)entier
DECIMAL(Precision)Real
DECIMAL(Precision, scale)Real
TINYINT UNSIGNEDUnsigned 1-byte integer
SMALLINT UNSIGNEDUnsigned 2-byte integer
INTEGER UNSIGNEDUnsigned 4-byte integer
BIGINT UNSIGNEDUnsigned 8-byte integer
TINYINTSigned 1-byte integer
SMALLINTSigned 2-byte integer
INTEGERSigned 4-byte integer
BIGINTSigned 8-byte integer
FLOAT4-byte real
REAL8-byte real
DOUBLE PRECISION8-byte real
MONEYCurrency
DATEDATE
DATETIMEDate time
TIMETime of day
CREATE TABLE AS
The CREATE TABLE AS statement describes a table and creates it from the result of a select query. The table columns have the names and types of data associated with the columns after the SELECT query has been run.
It uses the following format:
CREATE TABLE NomTable AS RequêteSélection [WITH NO DATA]
In this syntax:
  • SelectQuery is a SELECT query. The query is run and the table is created from the result of this Select query.
  • The "WITH NO DATA" option is used to create the table structure only (without the data found in the result of the Select query).
Caution: The index of the created table may differ from the one of the source table.
Remark: This type of query cannot be created with the wizard found in the query editor.
DROP TABLE
The DROP TABLE statement is used to physically delete a table. It uses the following format:
DROP TABLE [ IF EXISTS ] name [, ...]
Remarks:
  • IF EXISTS is used to avoid returning an error if the table does not exist.
  • This type of query cannot be created with the wizard found in the query editor.
ALTER TABLE
The ALTER TABLE statement is used to change the structure of an existing table. You can add or delete columns and indexes, change the type of the existing columns and rename the columns or the table. It uses the following format:
ALTER TABLE NomTable Action [, Action] ....
In this syntax:
  • the "Action" parameters correspond to one of the following actions:
    ADD [COLUMN] Description de rubrique
    ADD [COLUMN] (Description de rubrique1 [,Description de rubrique2]....)
    DROP [COLUMN] [IF EXISTS] NomRubrique
    DROP [COLUMN] [IF EXISTS] (NomRubrique1 [, NomRubrique2]...)
    ALTER [COLUMN] NomRubrique [SET DATA] TYPE alter_type_desc
    ALTER [COLUMN] NomRubrique SET DEFAULT <valeur>
    ALTER [COLUMN] NomRubrique DROP DEFAULT
    ADD [UNIQUE / PRIMARY KEY] INDEX [<NomIndex>] (Description d'index [, Description d'index2]...)
    DROP INDEX [IF EXISTS] NomIndex
    DROP INDEX [IF EXISTS] (NomIndex1 [, NomIndex2]...)
    RENAME COLUMN NomRubrique TO Nouveau_NomRubrique
    New in version 28
    ADD PRIMARY KEY INDEX adds a primary key in an existing file.
  • The "Description of item" parameters correspond to:
    NomRubrique type  [DEFAULT 'valeur' | DEFAULT NULL] [NOT NULL] [UNIQUE] [PRIMARY KEY]
     [AUTO_INCREMENT] [COMMENT 'libellé'] [COLLATE 'langue'] [SECUREPASSWORD]
    where:
    • ItemName: name that will be given to the item.
    • Type: Type of item (chosen among the types presented above).
    • DEFAULT: defines the default value of the item.
    • NOT NULL: prevents from assigning the NULL value to the item.
    • UNIQUE: defines an item with or without duplicates.
    • PRIMARY KEY: defines a primary key.
    • AUTO_INCREMENT: defines an automatic identifier for items of type Integer.
    • COMMENT: defines the caption of the item.
    • COLLATE: defines the language for sorting the item (for example: 'EN_US').
    • SECUREPASSWORD: is used to determine that the item is of type "Password".
  • the "Description of index" parameters correspond to:
    NomRubrique [ASC | DESC] [CI] [AI] [PI]
    where:
    • ASC and DESC: allows you to define the sorting direction.
    • CI: specifies that the case is ignored.
    • AI: specifies that accented characters are ignored.
    • PI: specifies that the punctuation is ignored.
Remarks:
  • This type of query cannot be created with the wizard found in the query editor.
  • To use the modified table with ALTER (without an analysis related to the project), use HDeclareExternal after the ALTER TABLE query is executed.
CREATE INDEX
The CREATE INDEX statement (equivalent to the ALTER TABLE statement) is used to create indexes. This statement is used to add indexes to an existing table. It uses the following format:
CREATE [UNIQUE] INDEX [NomIndex] ON NomTable (Description d'index [, Description d'index]... )
In this code, the "Description of index" parameters correspond to:
NomRubrique [ASC | DESC] [CI] [AI] [PI]
where:
  • ASC and DESC: allows you to define the sorting direction.
  • CI: specifies that the case is ignored.
  • AI: specifies that accented characters are ignored.
  • PI: specifies that the punctuation is ignored.
Remark: This type of query cannot be created with the wizard found in the query editor.
You have the ability to use the CREATE INDEX statement on a materialized view. In this case, the TableName parameter corresponds to the name of a materialized view. If the view is refreshed, the indexes are automatically updated: there is no need to re-create the indexes.
Example: Creating a materialized view and a composite key on this view.
-- Création d'une vue matérialisée
CREATE MATERIALIZED VIEW MaVueMaterialisee AS
SELECT
Client.Departement, Client.Aff, Client.TypeAdherent, COUNT(*) AS Qte
FROM Client
WHERE Client.Solde>0 AND Client.Type=2
AND Client.Famille IN ('A', 'D', 'O')
GROUP BY Client.Departement, Client.Aff, Client.TypeAdherent;
--';' pour pouvoir mettre une autre instruction SQL après
--Avec une clé composée
CREATE INDEX clecomp ON MaVueMaterialisee (Departement ASC CI AI PI,Aff ASC CI AI PI,TypeAdherent ASC)
DROP INDEX
The DROP INDEX statement is used to physically delete an index from a table. It uses the following format:
DROP INDEX  [ IF EXISTS ] NomIndex ON NomTable
where IF EXISTS: prevents returning an error if the index does not exist.
Remark: This type of query cannot be created with the wizard found in the query editor.
GRANT
GRANT queries are used to grant rights on an HFSQL server. This statement is equivalent to setting the element as hAllowed. It uses the following format:
  • Grant the requested rights on the specified tables to the specified users:
    GRANT droit[, droit [, ... ]] ON [TABLE] table [, table [, ...]]  
    TO utilisateur [, utilisateur [, ...]]
  • Grant the requested rights on the specified databases to the specified users:.
    GRANT droit[, droit [, ... ]] ON DATABASE database [, database [, ...]]
    TO utilisateur [, utilisateur [, ...]]
  • Grant the requested rights on the server.
    GRANT droit[, droit [, ... ]]
    TO utilisateur [, utilisateur [, ...]]
Remark:
  • This type of query cannot be created with the wizard found in the query editor.
  • These queries cannot be used on an HFSQL Classic database, nor on a database accessed via Native Connectors.
Available rights
The available rights are:
SQL rightHFSQL right
INSERThRightsInsert
LOCKhRightsLock
MANAGE RIGHTShRightsChangeRights
OWNERhRightsChangeOwner
MANAGE DUPLICATEhRightsManageDuplicate
SELECThRightsRead
MAINTENANCEhRightsMaintenance
ALTERhRightsAutoModif
UPDATEhRightsUpdate
DELETEhRightsDelete
DELETE FILEhRightsDeleteFile
REFERENCEShRightsChangeLink
CONNECThRightsConnection
ENCRYPTED CONNECThRightsEncryptedConnection
CREATEhRightsCreateFile
DEBUGhRightsDebug
RUN PROCEDUREhRightsRunProcedure
MANAGE REFERENCEShRightsManageIntegrity
MANAGE PROCEDUREhRightsManageProcedure
MANAGE TRIGGERhRightsManageTrigger
FORBID ACCESShRightsNoDatabaseAccess
BACKUPhRightsBackup
DELETE DATABASEhRightsDeleteDB
STOPhRightsStopServer
CHANGE PASSWORDhRightsChangePassword
CREATE DATABASEhRightsCreateDB
DISCONNECThRightsDisconnectClient
SEND MESSAGEhRightsSendMessageToClient
MANAGE TASKhRightsManageTask
MANAGE SERVERhRightsManageServer
MANAGE USERhRightsManageUser
READ LOGhRightsReadLogStat
PRIORITYhRightsPriority
REPLICATEhRightsServerReplication
SEE USERhRightsSeeUser
REVOKE
The REVOKE queries are used to remove rights (switch the rights to hInherit) on an HFSQL server. It uses the following format:
  • Set the requested rights as inherited on the specified tables for the specified users.
    REVOKE droit[, droit [, ... ]] ON [TABLE] table [, table [, ...]]  
    FROM utilisateur [, utilisateur [, ...]]
  • Set the requested rights as inherited on the specified databases for the specified users.
    REVOKE droit[, droit [, ... ]] ON DATABASE database [, database [, ...]]
    FROM utilisateur [, utilisateur [, ...]]
  • Set the requested rights as inherited on the server.
    REVOKE droit[, droit [, ... ]] FROM utilisateur [, utilisateur [, ...]]
Remark
  • This type of query cannot be created with the wizard found in the query editor.
  • These queries do not work with HFSQL Classic or Native Connectors.
Available rights: The available rights are identical to the SQL GRANT command. The table of rights is available with the GRANT command.
CREATE VIEW
CREATE VIEW is used to create an SQL view. SQL views are equivalent to temporary files in memory. In most cases, an SQL view is extracted from a file or from several files. A view can be handled by a SELECT command.
For more details on SQL views, see SQL view.
It uses the following format:
CREATE VIEW Vue [ ( Alias 1, Alias 2, ..., Alias N) ] AS RequêteSELECT
In this syntax:
  • View is the name of the view.
  • Alias 1, Alias 2, Alias N represent the alias names of the items from the SELECT query.
  • SELECTQuery is the SELECT query used to select the records found in the view.
SQL example: Creating an SQL view containing the company, the name and the phone of customers:
CREATE VIEW V_Clients
AS SELECT Société, Nom, Téléphone FROM Clients
DROP VIEW
DROP VIEW is used to delete a view.
DROP VIEW [IF EXISTS] Vue 1 [, Vue 2, ...Vue N] [RESTRICT | CASCADE]
  • If the IF EXISTS keyword is specified and the views do not exist, an error message is generated.
  • View 1, View 2, ... View N represent the list of views to delete.
  • If the RESTRICT keyword is specified, the view is not deleted if it is in use.
  • If the CASCADE keyword is specified, the view is deleted as well as the dependent views.
CREATE MATERIALIZED VIEW
A materialized view is a persistent view: a file is created on disk from the content of the view. This view can be handled later by an application other than the one that created it.
CREATE MATERIALIZED VIEW is used to create a materialized view.
CREATE MATERIALIZED VIEW Vue [ ( Alias 1, Alias 2, ...Alias N) ]
AS instruction SELECT [WITH [ NO ] DATA]
  • The WITH DATA keyword creates a view with data.
  • The WITH NO DATA keyword creates an empty view without data.
Remark
  • This type of query cannot be created with the wizard found in the query editor.
  • These queries cannot operate in HFSQL Classic or with Native Connectors.
DROP MATERIALIZED VIEW
DROP MATERIALIZED VIEW is used to physically delete a materialized view from the disk.
DROP MATERIALIZED VIEW [IF EXISTS] Vue 1 [, Vue 2, ...Vue N] [RESTRICT | CASCADE]
  • If the IF EXISTS keyword is specified and the views do not exist, an error message is generated.
  • View 1, View 2, ... View N represent the list of views to delete.
  • If the RESTRICT keyword is specified, the view is not deleted if it is in use.
  • If the CASCADE keyword is specified, the view is deleted as well as the dependent views.
REFRESH MATERIALIZED VIEW
REFRESH MATERIALIZED VIEW refreshes the content of a materialized view on the disk. The view is entirely recalculated.
REFRESH MATERIALIZED VIEW Vue [WITH [ NO ] DATA]
  • The WITH DATA keyword refreshes the view with the new data.
  • The WITH NO DATA keyword refreshes the empty view without data.
New in version 28
OPTIMIZE TABLE
The OPTIMIZE TABLE function allows to reorganize and reindex a data file and to rebuild the indexes of this file. This operation allows to improve the performance.
This function is equivalent to doing the WLanguage function HIndex for reorganization and reindexing of a data file.
OPTIMIZE TABLE NomFichier
FileName represents the name of the file to be reorganized.
Remark:
  • This function is locking: the data file is not accessible during the execution of the. You must make sure that the file is not in use when the function is called.
  • This function cannot be used with Native Connectors.
  • By Default , the reindexing performed corresponds to the following parameters: normal reindexing, full text index reindexing, reindexing in Background task, with a density rate of 80%. To use other parameters, use the HIndex function.
Minimum version required
  • Version 9
This page is also available for…
Comments
Click [Add] to post a comment