<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Database" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM ===			The ScriptForge library and its associated libraries are part of the LibreOffice project.				===
REM	===						The SFDatabases library is one of the associated libraries.									===
REM ===					Full documentation is available on https://help.libreoffice.org/								===
REM =======================================================================================================================

Option Compatible
Option ClassModule

Option Explicit

&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos;	SF_Database
&apos;&apos;&apos;	===========
&apos;&apos;&apos;		Management of databases embedded in or related to Base documents
&apos;&apos;&apos;		Each instance of the current class represents a single database, with essentially its tables, queries and data
&apos;&apos;&apos;
&apos;&apos;&apos;		The exchanges with the database are done in SQL only.
&apos;&apos;&apos;		To make them more readable, use optionally square brackets to surround table/query/field names
&apos;&apos;&apos;		instead of the (RDBMS-dependent) normal surrounding character (usually, double-quote, single-quote or other).
&apos;&apos;&apos;		SQL statements may be run in direct or indirect mode. In direct mode the statement is transferred literally
&apos;&apos;&apos;		without syntax checking nor review to the database system.
&apos;&apos;&apos;
&apos;&apos;&apos;		The provided interfaces include simple tables, queries and fields lists, and access to database metadata.
&apos;&apos;&apos;
&apos;&apos;&apos;		Transaction handling
&apos;&apos;&apos;			Changes to data remain reversible until the moment the running script instructs the database to commit them.
&apos;&apos;&apos;			The implicit (default) behaviour is that commit takes place after the execution of every single SQL statement.
&apos;&apos;&apos;			The choice can be made (SetTransactionMode()) to take commitments manually.
&apos;&apos;&apos;			The Commit() and Rollback() statements delimit transactions.
&apos;&apos;&apos;
&apos;&apos;&apos;		Service invocation and usage:
&apos;&apos;&apos;		1) To access any database at anytime
&apos;&apos;&apos;				Dim myDatabase As Object
&apos;&apos;&apos;				Set myDatabase = CreateScriptService(&quot;SFDatabases.Database&quot;, FileName, , [ReadOnly], [User, [Password]])
&apos;&apos;&apos;					&apos;	Args:
&apos;&apos;&apos;					&apos;		FileName:	the name of the Base file compliant with the SF_FileSystem.FileNaming notation
&apos;&apos;&apos;					&apos;		RegistrationName: the name of a registered database (mutually exclusive with FileName)
&apos;&apos;&apos;					&apos;		ReadOnly:	Default = True
&apos;&apos;&apos;					&apos;		User, Password:	additional connection arguments to the database server
&apos;&apos;&apos;				&apos;	... Run queries, SQL statements, ...
&apos;&apos;&apos;				myDatabase.CloseDatabase()
&apos;&apos;&apos;
&apos;&apos;&apos;		2) To access the database related to the current Base document
&apos;&apos;&apos;				Dim myDoc As Object, myDatabase As Object, ui As Object
&apos;&apos;&apos;				Set ui = CreateScriptService(&quot;UI&quot;)
&apos;&apos;&apos;				Set myDoc = ui.OpenBaseDocument(&quot;myDb.odb&quot;)
&apos;&apos;&apos;				Set myDatabase = myDoc.GetDatabase()	&apos;	user and password are supplied here, if needed
&apos;&apos;&apos;				&apos;	... Run queries, SQL statements, ...
&apos;&apos;&apos;				myDoc.CloseDocument()
&apos;&apos;&apos;
&apos;&apos;&apos;		Detailed user documentation:
&apos;&apos;&apos;			https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_database.html?DbPAR=BASIC
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;

REM ================================================================== EXCEPTIONS

Private Const DBREADONLYERROR		=	&quot;DBREADONLYERROR&quot;
Private Const SQLSYNTAXERROR		=	&quot;SQLSYNTAXERROR&quot;
Private Const SQLSYNTAX2ERROR		=	&quot;SQLSYNTAX2ERROR&quot;

REM ============================================================= PRIVATE MEMBERS

Private [Me]					As Object
Private [_Parent]				As Object
Private ObjectType				As String		&apos; Must be DATABASE
Private ServiceName				As String
Private _DataSource				As Object		&apos; com.sun.star.comp.dba.ODatabaseSource
Private	_Connection				As Object		&apos; com.sun.star.sdbc.XConnection
Private	_URL					As String		&apos; Text on status bar
Private _Location				As String		&apos; File name
Private _ReadOnly				As Boolean
Private	_MetaData				As Object		&apos; com.sun.star.sdbc.XDatabaseMetaData
Private _User					As String		&apos; Connection parameters to enable a reconnection
Private _Password				As String
Private _Datasets				As Variant		&apos; Array of open datasets

REM ============================================================ MODULE CONSTANTS

Const cstToken = &quot;//&quot;							&apos; Form names accept special characters but not slashes

REM ===================================================== CONSTRUCTOR/DESTRUCTOR

REM -----------------------------------------------------------------------------
Private Sub Class_Initialize()
	Set [Me] = Nothing
	Set [_Parent] = Nothing
	ObjectType = &quot;DATABASE&quot;
	ServiceName = &quot;SFDatabases.Database&quot;
	Set _DataSource = Nothing
	Set _Connection = Nothing
	_URL = &quot;&quot;
	_Location = &quot;&quot;
	_ReadOnly = True
	Set _MetaData = Nothing
	_User = &quot;&quot;
	_Password = &quot;&quot;
	_Datasets = Array()
End Sub		&apos;	SFDatabases.SF_Database Constructor

REM -----------------------------------------------------------------------------
Private Sub Class_Terminate()
	Call Class_Initialize()
End Sub		&apos;	SFDatabases.SF_Database Destructor

REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
	Call Class_Terminate()
	Set Dispose = Nothing
End Function	&apos;	SFDatabases.SF_Database Explicit Destructor

REM ================================================================== PROPERTIES

REM -----------------------------------------------------------------------------
Property Get Queries() As Variant
&apos;&apos;&apos;	Return the list of available queries in the database
	Queries = _PropertyGet(&quot;Queries&quot;)
End Property	&apos;	SFDatabases.SF_Database.Queries (get)

REM -----------------------------------------------------------------------------
Property Get Tables() As Variant
&apos;&apos;&apos;	Return the list of available Tables in the database
	Tables = _PropertyGet(&quot;Tables&quot;)
End Property	&apos;	SFDatabases.SF_Database.Tables (get)

REM -----------------------------------------------------------------------------
Property Get XConnection() As Variant
&apos;&apos;&apos;	Return a com.sun.star.sdbc.XConnection UNO object
	XConnection = _PropertyGet(&quot;XConnection&quot;)
End Property	&apos;	SFDatabases.SF_Database.XConnection (get)

REM -----------------------------------------------------------------------------
Property Get XMetaData() As Variant
&apos;&apos;&apos;	Return a com.sun.star.sdbc.XDatabaseMetaData UNO object
	XMetaData = _PropertyGet(&quot;XMetaData&quot;)
End Property	&apos;	SFDatabases.SF_Database.XMetaData (get)

REM ===================================================================== METHODS

REM -----------------------------------------------------------------------------
Public Sub CloseDatabase()
&apos;&apos;&apos;	Close the current database connection

Const cstThisSub = &quot;SFDatabases.Database.CloseDatabase&quot;
Const cstSubArgs = &quot;&quot;

	On Local Error GoTo 0		&apos;	Disable useless error checking

Check:
	ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

Try:
	_CloseConnection()
	Dispose()

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
End Sub			&apos;	SFDatabases.SF_Database.CloseDatabase

REM -----------------------------------------------------------------------------
Public Sub Commit()
&apos;&apos;&apos;	Commit all updates done since the previous Commit or Rollback
&apos;&apos;&apos;	The statement is ignored if the commits are done automatically after each SQL statement.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DBREADONLYERROR			The method is not applicable on a read-only database
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		db.SetTransactionMode(4)		&apos;	Highest transaction level
&apos;&apos;&apos;		db.RunSql(&quot;UPDATE ...&quot;)
&apos;&apos;&apos;		db.Commit()
&apos;&apos;&apos;		db.RunSql(DELETE ...&quot;)
&apos;&apos;&apos;		If ...something happened... Then db.Rollback() Else db.Commit()
&apos;&apos;&apos;		db.SetTransactionMode()			&apos;	Back to the automatic mode

Const cstThisSub = &quot;SFDatabases.Database.Commit&quot;
Const cstSubArgs = &quot;&quot;

	On Local Error GoTo Finally

Check:
	ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
	If _ReadOnly Then GoTo Catch_ReadOnly

Try:
	With _Connection
		If Not .AutoCommit Then
			.commit()
			&apos;	To make updates potentially visible in the user interface ...
			_FlushConnection()
		End If
	End With

Finally:
	On Local Error GoTo 0
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch_ReadOnly:
	ScriptForge.SF_Exception.RaiseFatal(DBREADONLYERROR)
	GoTo Finally
End Sub			&apos;	SFDatabases.SF_Database.Commit

REM -----------------------------------------------------------------------------
Public Function CreateDataset(Optional ByVal SQLCommand As Variant _
									, Optional ByVal DirectSql As Variant _
									, Optional ByVal Filter As Variant _
									, Optional ByVal OrderBy As Variant _
									) As Object
&apos;&apos;&apos;	Create and return a Dataset class instance based on a table, a query
&apos;&apos;&apos;	or an SQL SELECT statement.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		SQLCommand: as a case-sensitive string, a table name, a query name
&apos;&apos;&apos;			or a valid SQL SELECT statement. Identifiers may be surrounded
&apos;&apos;&apos;			with square brackets
&apos;&apos;&apos;		DirectSql: when True, the statement is processed by the targeted RDBMS
&apos;&apos;&apos;		Filter: an additional condition that records must match, expressed
&apos;&apos;&apos;			as a valid SQL WHERE clause without the WHERE keyword
&apos;&apos;&apos;		OrderBy: the ordering of the dataset expressed as a valid SQL ORDER BY clause
&apos;&apos;&apos;			without the ORDER BY keywords
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A SF_Dataset instance or Nothing when not successful
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		SQLSYNTAX2ERROR			The given SQL statement is incorrect

Dim oDataset As Object			&apos;	Return value
Dim bDirect As Boolean			&apos;	Alias of DirectSql
Dim sSql As String				&apos;	SQL statement
Dim sType As String				&apos;	TABLE, QUERY or SQL
Dim oQuery As Object			&apos;	com.sun.star.ucb.XContent
Dim ARR As Object				:	Set ARR = ScriptForge.SF_Array

Const cstThisSub = &quot;SFDatabases.Database.CreateDataset&quot;
Const cstSubArgs = &quot;SQLCommand, [DirectSQL=False], [Filter=&quot;&quot;&quot;&quot;], [OrderBy=&quot;&quot;&quot;&quot;]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set oDataset = Nothing

Check:
	If IsMissing(DirectSQL) Or IsEmpty(DirectSQL) Then DirectSQL = False
	If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = &quot;&quot;
	If IsMissing(OrderBy) Or IsEmpty(OrderBy) Then OrderBy = &quot;&quot;
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(SQLCommand, &quot;SQLCommand&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(DirectSQL, &quot;DirectSQL&quot;, ScriptForge.V_BOOLEAN) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(Filter, &quot;Filter&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(OrderBy, &quot;OrderBy&quot;, V_STRING) Then GoTo Finally
	End If

Try:
	&apos;	Table, query of SQL ? Prepare dataset
	If ARR.Contains(Tables, SQLCommand, CaseSensitive := True, SortOrder := &quot;ASC&quot;) Then
		If Len(Filter) + Len(OrderBy) = 0 Then		&apos;	Filter seems not applicable on pure TABLE resultset
			sType = &quot;TABLE&quot;
			sSql = SQLCommand
		Else
			sType = &quot;SQL&quot;
			sSql = &quot;SELECT * FROM [&quot; &amp; SQLCommand &amp; &quot;]&quot;
		End If
		bDirect = DirectSQL
	ElseIf ARR.Contains(Queries, SQLCommand, CaseSensitive := True, SortOrder := &quot;ASC&quot;) Then
		Set oQuery = _Connection.Queries.getByName(SQLCommand)
		If Len(Filter) + Len(OrderBy) = 0 Then		&apos;	Filter seems not applicable on pure QUERY resultset
			sType = &quot;QUERY&quot;
			sSql = SQLCommand
		Else
			sType = &quot;SQL&quot;
			sSql = oQuery.Command
		End If
		bDirect = Not oQuery.EscapeProcessing
	ElseIf ScriptForge.SF_String.StartsWith(SQLCommand, &quot;SELECT&quot;, CaseSensitive := False) Then
		sType = &quot;SQL&quot;
		sSql = SQLCommand
		bDirect = DirectSQL
	Else
		If Not ScriptForge.SF_Utils._Validate(SQLCommand, &quot;SQLCommand&quot;, V_STRING _
			, ARR.Flatten(ARR.Append(Tables, Queries))) Then GoTo Finally
	End If

	Set oDataset = New SF_Dataset
	With oDataset
		Set .[Me] = oDataset
		Set ._ParentDatabase = [Me]
		._DatasetType = sType
		._Command = SQLCommand
		._Sql = _ReplaceSquareBrackets(sSql)
		._DirectSql = bDirect
		._Filter = _ReplaceSquareBrackets(Filter)
		._OrderBy = _ReplaceSquareBrackets(OrderBy)
		._ReadOnly = _ReadOnly
		&apos;	If creation not successful, then cancel everything
		If Not ._Initialize() Then Set oDataset = .Dispose()
	End With

Finally:
	Set CreateDataset = oDataset
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.CreateDataset

REM -----------------------------------------------------------------------------
Public Function DAvg(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function AVG() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE

	DAvg = _DFunction(&quot;Avg&quot;, Expression, TableName, Criteria)

End Function	&apos;	SFDatabases.SF_Database.DAvg

REM -----------------------------------------------------------------------------
Public Function DCount(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function COUNT() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE

	DCount = _DFunction(&quot;Count&quot;, Expression, TableName, Criteria)

End Function	&apos;	SFDatabases.SF_Database.DCount

REM -----------------------------------------------------------------------------
Public Function DLookup(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								, Optional ByVal OrderClause As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function Lookup() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	To order the results, a pvOrderClause may be precised. The 1st record will be retained.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE
&apos;&apos;&apos;		pvOrderClause:	an optional order clause incl. &quot;DESC&quot; if relevant

	DLookup = _DFunction(&quot;Lookup&quot;, Expression, TableName, Criteria, OrderClause)

End Function	&apos;	SFDatabases.SF_Database.DLookup

REM -----------------------------------------------------------------------------
Public Function DMax(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function MAX() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE

	DMax = _DFunction(&quot;Max&quot;, Expression, TableName, Criteria)

End Function	&apos;	SFDatabases.SF_Database.DMax

REM -----------------------------------------------------------------------------
Public Function DMin(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function MIN() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE

	DMin = _DFunction(&quot;Min&quot;, Expression, TableName, Criteria)

End Function	&apos;	SFDatabases.SF_Database.DMin

REM -----------------------------------------------------------------------------
Public Function DSum(Optional ByVal Expression As Variant _
								, Optional ByVal TableName As Variant _
								, Optional ByVal Criteria As Variant _
								) As Variant
&apos;&apos;&apos;	Compute the aggregate function Sum() on a  field or expression belonging to a table
&apos;&apos;&apos;	filtered by a WHERE-clause.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Expression:		an SQL expression
&apos;&apos;&apos;		TableName:		the name of a table
&apos;&apos;&apos;		Criteria:		an optional WHERE clause without the word WHERE

	DSum = _DFunction(&quot;Sum&quot;, Expression, TableName, Criteria)

End Function	&apos;	SFDatabases.SF_Database.DSum

REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
&apos;&apos;&apos;	Return the actual value of the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The actual value of the property
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDatabase.GetProperty(&quot;Queries&quot;)

Const cstThisSub = &quot;SFDatabases.Database.GetProperty&quot;
Const cstSubArgs = &quot;&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	GetProperty = Null

Check:
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	GetProperty = _PropertyGet(PropertyName)

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.GetProperty

REM -----------------------------------------------------------------------------
Public Function GetRows(Optional ByVal SQLCommand As Variant _
							, Optional ByVal DirectSQL As Variant _
							, Optional ByVal Header As Variant _
							, Optional ByVal MaxRows As Variant _
							) As Variant
&apos;&apos;&apos;	Return the content of a table, a query or a SELECT SQL statement as an array
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		SQLCommand: a table name, a query name or a SELECT SQL statement
&apos;&apos;&apos;		DirectSQL: 	when True, no syntax conversion is done by LO. Default = False
&apos;&apos;&apos;					Ignored when SQLCommand is a table or a query name
&apos;&apos;&apos;		Header:		When True, a header row is inserted on the top of the array with the column names. Default = False
&apos;&apos;&apos;		MaxRows:	The maximum number of returned rows. If absent, all records are returned
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		a 2D array(row, column), even if only 1 column and/or 1 record
&apos;&apos;&apos;		an empty array if no records returned
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim a As Variant
&apos;&apos;&apos;			a = myDatabase.GetRows(&quot;SELECT [First Name], [Last Name] FROM [Employees] ORDER BY [Last Name]&quot;, Header := True)

Dim vResult As Variant			&apos;	Return value
Dim oResult As Object			&apos;	com.sun.star.sdbc.XResultSet
Dim oQuery As Object			&apos;	com.sun.star.ucb.XContent
Dim sSql As String				&apos;	SQL statement
Dim bDirect						&apos;	Alias of DirectSQL
Dim lCols As Long				&apos;	Number of columns
Dim lRows As Long				&apos;	Number of rows
Dim oColumns As Object			&apos;	Collection of com.sun.star.sdb.ODataColumn
Dim bRead As Boolean			&apos;	When True, next record has been read successfully
Dim i As Long
Const cstThisSub = &quot;SFDatabases.Database.GetRows&quot;
Const cstSubArgs = &quot;SQLCommand, [DirectSQL=False], [Header=False], [MaxRows=0]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	vResult = Array()

Check:
	If IsMissing(DirectSQL) Or IsEmpty(DirectSQL) Then DirectSQL = False
	If IsMissing(Header) Or IsEmpty(Header) Then Header = False
	If IsMissing(MaxRows) Or IsEmpty(MaxRows) Then MaxRows = 0
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(SQLCommand, &quot;SQLCommand&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(DirectSQL, &quot;DirectSQL&quot;, ScriptForge.V_BOOLEAN) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(Header, &quot;Header&quot;, ScriptForge.V_BOOLEAN) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(MaxRows, &quot;MaxRows&quot;, ScriptForge.V_NUMERIC) Then GoTo Finally
	End If

Try:
	&apos;	Table, query of SQL ? Prepare resultset
	If ScriptForge.SF_Array.Contains(Tables, SQLCommand, CaseSensitive := True, SortOrder := &quot;ASC&quot;) Then
		sSql = &quot;SELECT * FROM [&quot; &amp; SQLCommand &amp; &quot;]&quot;
		bDirect = True
	ElseIf ScriptForge.SF_Array.Contains(Queries, SQLCommand, CaseSensitive := True, SortOrder := &quot;ASC&quot;) Then
		Set oQuery = _Connection.Queries.getByName(SQLCommand)
		sSql = oQuery.Command
		bDirect = Not oQuery.EscapeProcessing
	ElseIf ScriptForge.SF_String.StartsWith(SQLCommand, &quot;SELECT&quot;, CaseSensitive := False) Then
		sSql = SQLCommand
		bDirect = DirectSQL
	Else
		GoTo Finally
	End If

	&apos;	Execute command
	Set oResult = _ExecuteSql(sSql, bDirect)
	If IsNull(oResult) Then GoTo Finally

	With oResult
		&apos;Initialize output array with header row
		Set oColumns = .getColumns()
		lCols = oColumns.Count - 1
		If Header Then
			lRows = 0
			ReDim vResult(0 To lRows, 0 To lCols)
			For i = 0 To lCols
				vResult(lRows, i) = oColumns.getByIndex(i).Name
			Next i
			If MaxRows &gt; 0 Then MaxRows = MaxRows + 1
		Else
			lRows = -1
		End If

	&apos;	Load data
		bRead = .first()
		Do While bRead And (MaxRows = 0 Or lRows &lt; MaxRows - 1)
			lRows = lRows + 1
			If lRows = 0 Then
				ReDim vResult(0 To lRows, 0 To lCols)
			Else
				ReDim Preserve vResult(0 To lRows, 0 To lCols)
			End If
			For i = 0 To lCols
				vResult(lRows, i) = _GetColumnValue(oResult, i + 1)
			Next i
			bRead = .next()
		Loop
	End With
	
Finally:
	GetRows = vResult
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.GetRows

REM -----------------------------------------------------------------------------
Public Function Methods() As Variant
&apos;&apos;&apos;	Return the list of public methods of the Database service as an array

	Methods = Array( _
					&quot;CloseDatabase&quot; _
					, &quot;Commit&quot; _
					, &quot;CreateDataset&quot; _
					, &quot;DAvg&quot; _
					, &quot;DCount&quot; _
					, &quot;DLookup&quot; _
					, &quot;DMax&quot; _
					, &quot;DMin&quot; _
					, &quot;DSum&quot; _
					, &quot;GetRows&quot; _
					, &quot;OpenFormDocument&quot; _
					, &quot;OpenQuery&quot; _
					, &quot;OpenSql&quot; _
					, &quot;OpenTable&quot; _
					, &quot;Rollback&quot; _
					, &quot;RunSql&quot; _
					, &quot;SetTransactionMode&quot; _
					)

End Function	&apos;	SFDatabases.SF_Database.Methods

REM -----------------------------------------------------------------------------
Public Function OpenFormDocument(Optional ByVal FormDocument As Variant) As Object
&apos;&apos;&apos;	Open the FormDocument given by its hierarchical name in normal mode
&apos;&apos;&apos;	If the form document is already open, the form document is made active
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		FormDocument: a valid form document name as a case-sensitive string
&apos;&apos;&apos;			When hierarchical, the hierarchy must be rendered with forward slashes (&quot;/&quot;)
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A FormDocument instance or Nothing
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		Form name is invalid
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Set oForm = oDb.OpenFormDocument(&quot;Folder1/myFormDocument&quot;)

Dim oOpen As Object					&apos;	Return value
Dim oFormDocuments As Variant		&apos;	com.sun.star.comp.dba.ODocumentContainer
Dim vFormNames As Variant			&apos;	Array of all document form names present in the document
Dim vOpenArgs As Variant			&apos;	Array of property values
Dim oNewForm As Object				&apos;	Output of loadComponent()
Const cstThisSub = &quot;SFDatabases.Database.OpenFormDocument&quot;
Const cstSubArgs = &quot;FormDocument&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set oOpen = Nothing

Check:
	&apos;	Build list of available FormDocuments recursively with _CollectFormDocuments
	Set oFormDocuments = _Connection.Parent.DataBaseDocument.FormDocuments

	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		vFormNames = Split(_CollectFormDocuments(oFormDocuments), cstToken)
		If Not ScriptForge.SF_Utils._Validate(FormDocument, &quot;FormDocument&quot;, V_STRING, vFormNames, True) Then GoTo Finally
	End If

Try:
	vOpenArgs = Array(SF_Utils._MakePropertyValue(&quot;ActiveConnection&quot;, _Connection) _
						, SF_Utils._MakePropertyValue(&quot;OpenMode&quot;, &quot;open&quot;) _
				)
	Set oNewForm = oFormDocuments.loadComponentFromURL(FormDocument, &quot;&quot;, 0, vOpenArgs)

	Set oOpen = ScriptForge.SF_Services.CreateScriptService(&quot;SFDocuments.FormDocument&quot;, oNewForm)

	&apos;	Prevent desynchronization when using .last(), .next() etc immediately after component loading. Bug #156836
	Wait 1

Finally:
	Set OpenFormDocument = oOpen
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SF_Databases.SF_Database.OpenFormDocument

REM -----------------------------------------------------------------------------
Public Function OpenQuery(Optional ByVal QueryName As Variant) As Object
&apos;&apos;&apos;	Open the query given by its name
&apos;&apos;&apos;	The datasheet will live independently from any other (typically Base) component
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		QueryName: a valid query name as a case-sensitive string
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A Datasheet class instance if the query could be opened, otherwise Nothing
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		Query name is invalid
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		oDb.OpenQuery(&quot;myQuery&quot;)

Dim oOpen As Object				&apos;	Return value
Const cstThisSub = &quot;SFDatabases.Database.OpenQuery&quot;
Const cstSubArgs = &quot;QueryName&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set oOpen = Nothing

Check:
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(QueryName, &quot;QueryName&quot;, V_STRING, Queries, True) Then GoTo Finally
	End If

Try:
	Set oOpen = _OpenDatasheet(QueryName, com.sun.star.sdb.CommandType.QUERY _
						, _Connection.Queries.getByName(QueryName).EscapeProcessing)

Finally:
	Set OpenQuery = oOpen
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;SFDatabases.SF_Database.OpenQuery

REM -----------------------------------------------------------------------------
Public Function OpenSql(Optional ByRef Sql As Variant _
							, Optional ByVal DirectSql As Variant _
							) As Object
&apos;&apos;&apos;	Open the datasheet based on a SQL SELECT statement.
&apos;&apos;&apos;	The datasheet will live independently from any other (typically Base) component
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Sql:	a valid Sql statement as a case-sensitive string.
&apos;&apos;&apos;				Identifiers may be surrounded by square brackets
&apos;&apos;&apos;		DirectSql: when True, the statement is processed by the targeted RDBMS
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A Datasheet class instance if it could be opened, otherwise Nothing
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		oDb.OpenSql(&quot;SELECT * FROM [Customers] ORDER BY [CITY]&quot;)

Dim oOpen As Object				&apos;	Return value
Const cstThisSub = &quot;SFDatabases.Database.OpenSql&quot;
Const cstSubArgs = &quot;Sql, [DirectSql=False]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set oOpen = Nothing

Check:
	If IsMissing(DirectSql) Or IsEmpty(DirectSql) Then DirectSql = False
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(Sql, &quot;Sql&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(DirectSql, &quot;DirectSql&quot;, ScriptForge.V_BOOLEAN) Then GoTo Finally
	End If

Try:
	Set oOpen = _OpenDatasheet(_ReplaceSquareBrackets(Sql), com.sun.star.sdb.CommandType.COMMAND, Not DirectSql)

Finally:
	Set OpenSql = oOpen
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.OpenSql

REM -----------------------------------------------------------------------------
Public Function OpenTable(Optional ByVal TableName As Variant) As Object
&apos;&apos;&apos;	Open the table given by its name
&apos;&apos;&apos;	The datasheet will live independently from any other (typically Base) component
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		TableName: a valid table name as a case-sensitive string
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A Datasheet class instance if the table could be opened, otherwise Nothing
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		Table name is invalid
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		oDb.OpenTable(&quot;myTable&quot;)

Dim oOpen As Object				&apos;	Return value
Const cstThisSub = &quot;SFDatabases.Database.OpenTable&quot;
Const cstSubArgs = &quot;TableName&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set oOpen = Nothing

Check:
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(TableName, &quot;TableName&quot;, V_STRING, Tables, True) Then GoTo Finally
	End If

Try:
	Set oOpen = _OpenDatasheet(TableName, com.sun.star.sdb.CommandType.TABLE, True)

Finally:
	Set OpenTable = oOpen
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.OpenTable

REM -----------------------------------------------------------------------------
Public Function Properties() As Variant
&apos;&apos;&apos;	Return the list or properties of the Database class as an array

	Properties = Array( _
					&quot;Queries&quot; _
					, &quot;Tables&quot; _
					, &quot;XConnection&quot; _
					, &quot;XMetaData&quot; _
					)

End Function	&apos;	SFDatabases.SF_Database.Properties

REM -----------------------------------------------------------------------------
Public Sub Rollback()
&apos;&apos;&apos;	Cancel all updates done since the previous Commit or Rollback
&apos;&apos;&apos;	The statement is ignored if the commits are done automatically after each SQL statement.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DBREADONLYERROR			The method is not applicable on a read-only database
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		db.SetTransactionMode(4)		&apos;	Highest transaction level
&apos;&apos;&apos;		db.RunSql(&quot;UPDATE ...&quot;)
&apos;&apos;&apos;		db.Commit()
&apos;&apos;&apos;		db.RunSql(DELETE ...&quot;)
&apos;&apos;&apos;		If ...something happened... Then db.Rollback() Else db.Commit()
&apos;&apos;&apos;		db.SetTransactionMode()			&apos;	Back to the automatic mode

Const cstThisSub = &quot;SFDatabases.Database.Rollback&quot;
Const cstSubArgs = &quot;&quot;

	On Local Error GoTo Finally

Check:
	ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
	If _ReadOnly Then GoTo Catch_ReadOnly

Try:
	With _Connection
		If Not .AutoCommit Then .rollback()
	End With

Finally:
	On Local Error GoTo 0
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch_ReadOnly:
	ScriptForge.SF_Exception.RaiseFatal(DBREADONLYERROR)
	GoTo Finally
End Sub			&apos;	SFDatabases.SF_Database.Rollback

REM -----------------------------------------------------------------------------
Public Function RunSql(Optional ByVal SQLCommand As Variant _
							, Optional ByVal DirectSQL As Variant _
							) As Boolean
&apos;&apos;&apos;	Execute an action query (table creation, record insertion, ...) or SQL statement on the current database
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		SQLCommand: a query name or an SQL statement
&apos;&apos;&apos;		DirectSQL: 	when True, no syntax conversion is done by LO. Default = False
&apos;&apos;&apos;					Ignored when SQLCommand is a query name
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DBREADONLYERROR			The method is not applicable on a read-only database
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		myDatabase.RunSql(&quot;INSERT INTO [EMPLOYEES] VALUES(25, &apos;SMITH&apos;, &apos;John&apos;)&quot;, DirectSQL := True)

Dim bResult As Boolean			&apos;	Return value
Dim oStatement As Object		&apos;	com.sun.star.sdbc.XStatement
Dim oQuery As Object			&apos;	com.sun.star.ucb.XContent
Dim sSql As String				&apos;	SQL statement
Dim bDirect						&apos;	Alias of DirectSQL
Const cstQuery = 2, cstSql = 3
Const cstThisSub = &quot;SFDatabases.Database.RunSql&quot;
Const cstSubArgs = &quot;SQLCommand, [DirectSQL=False]&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bResult = False

Check:
	If IsMissing(DirectSQL) Or IsEmpty(DirectSQL) Then DirectSQL = False
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(SQLCommand, &quot;SQLCommand&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(DirectSQL, &quot;DirectSQL&quot;, ScriptForge.V_BOOLEAN) Then GoTo Finally
	End If
	If _ReadOnly Then GoTo Catch_ReadOnly

Try:
	&apos;	Query of SQL ?
	If ScriptForge.SF_Array.Contains(Queries, SQLCommand, CaseSensitive := True, SortOrder := &quot;ASC&quot;) Then
		Set oQuery = _Connection.Queries.getByName(SQLCommand)
		sSql = oQuery.Command
		bDirect = Not oQuery.EscapeProcessing
	ElseIf Not ScriptForge.SF_String.StartsWith(SQLCommand, &quot;SELECT&quot;, CaseSensitive := False) Then
		sSql = SQLCommand
		bDirect = DirectSQL
	Else
		GoTo Finally
	End If

	&apos;	Execute command
	bResult = _ExecuteSql(sSql, bDirect)
	
Finally:
	RunSql = bResult
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
Catch_ReadOnly:
	ScriptForge.SF_Exception.RaiseFatal(DBREADONLYERROR)
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.RunSql

REM -----------------------------------------------------------------------------
Public Function SetProperty(Optional ByVal PropertyName As Variant _
								, Optional ByRef Value As Variant _
								) As Boolean
&apos;&apos;&apos;	Set a new value to the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;		Value: its new value
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist

Const cstThisSub = &quot;SFDatabases.Database.SetProperty&quot;
Const cstSubArgs = &quot;PropertyName, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	SetProperty = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	Select Case UCase(PropertyName)
		Case Else
	End Select

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.SetProperty

REM -----------------------------------------------------------------------------
Public Function SetTransactionMode(Optional ByVal TransactionMode As Variant) As Boolean
&apos;&apos;&apos;	Configure the handling of transactions.
&apos;&apos;&apos;	Usually all transactions are in auto-commit mode, that means, a commit takes place
&apos;&apos;&apos;	after each single SQL command. Therefore to control a transaction manually, implies to switch auto-commit off.
&apos;&apos;&apos;	The first SQL command starts a transaction that is active until the corresponding
&apos;&apos;&apos;	methods have been committed or rolled back.
&apos;&apos;&apos;
&apos;&apos;&apos;	The transaction mode remains valid until the next call of the method with a different value,
&apos;&apos;&apos;	or until the closure of the actual Database instance,
&apos;&apos;&apos;	or until a call to SetTransactionMode() without argument, which cancels the manual transaction mode.
&apos;&apos;&apos;
&apos;&apos;&apos;	The method may close and replace the actual  connection. This means that all open datasets
&apos;&apos;&apos;	are first closed. Open datasheets might see their content changed or vanish.
&apos;&apos;&apos;	The easiest is to set the transaction mode immediately after the creation of the Database instance.
&apos;&apos;&apos;
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		TransactionMode: one of the com.sun.star.sdbc.TransactionIsolation constants:
&apos;&apos;&apos;			(0)	NONE				Indicates that transactions are not supported. Default: cancel the transaction mode.
&apos;&apos;&apos;			(1) READ_UNCOMMITTED	Dirty reads, non-repeatable reads and phantom reads can occur.
&apos;&apos;&apos;									This level allows a row changed by one transaction to be read by another transaction
&apos;&apos;&apos;									before any changes in that row have been committed (a &quot;dirty read&quot;).
&apos;&apos;&apos;									If any of the changes are rolled back, the second transaction will have retrieved an invalid row. 
&apos;&apos;&apos;			(2) READ_COMMITTED		Dirty reads are prevented; non-repeatable reads and phantom reads can occur.
&apos;&apos;&apos;									This level only prohibits a transaction from reading a row with uncommitted changes in it.
&apos;&apos;&apos;			(4)	REPEATABLE_READ		Dirty reads and non-repeatable reads are prevented; phantom reads can occur.
&apos;&apos;&apos;									This level prohibits a transaction from reading a row with uncommitted changes in it,
&apos;&apos;&apos;									and it also prohibits the situation where one transaction reads a row,
&apos;&apos;&apos;									a second transaction alters the row, and the first transaction rereads the row,
&apos;&apos;&apos;									getting different values the second time (a &quot;non-repeatable read&quot;).
&apos;&apos;&apos;			(8) SERIALIZABLE		Dirty reads, non-repeatable reads and phantom reads are prevented.
&apos;&apos;&apos;									This level includes the prohibitions in REPEATABLE_READ and further prohibits
&apos;&apos;&apos;									the situation where one transaction reads all rows that satisfy a WHERE condition,
&apos;&apos;&apos;									a second transaction inserts a row that satisfies that WHERE condition,
&apos;&apos;&apos;									and the first transaction rereads for the same condition, retrieving
&apos;&apos;&apos;									the additional &quot;phantom&quot; row in the second read. 
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True when successful.
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DBREADONLYERROR			The method is not applicable on a read-only database
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		oDb.SetTransactionMode(com.sun.star.sdbc.TransactionIsolation.SERIALIZABLE)		&apos;	8

Dim bSet As Boolean				&apos;	Return value
Dim bCommit As Boolean			&apos;	To compare with AutoCommit
Const cstThisSub = &quot;SFDatabases.Database.SetTransactionMode&quot;
Const cstSubArgs = &quot;TransactionMode=0|1|2|4|8&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bSet = False

Check:
	If IsMissing(TransactionMode) Or IsEmpty(TransactionMode) Then TransactionMode = 0
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(TransactionMode, &quot;TransactionMode&quot;, ScriptForge.V_NUMERIC, Array(0, 1, 2, 4, 8)) Then GoTo Finally
	End If
	If _ReadOnly Then GoTo Catch_ReadOnly

Try:
	bCommit = ( TransactionMode &gt; com.sun.star.sdbc.TransactionIsolation.NONE )
	&apos;	Replace the existing connection
	If Not IsNull(_Connection) Then
		With _Connection
			If .AutoCommit And bCommit Then
				_CloseConnection()
				Set _Connection = _DataSource.getIsolatedConnection(_User, _Password)
			ElseIf Not .AutoCommit And Not bCommit Then
				_CloseConnection()
				Set _Connection = _DataSource.getConnection(_User, _Password)
			End If
		End With
	End If

		&apos;	Set the transaction mode
	With _Connection
		If bCommit Then
			.SetTransactionIsolation(CLng(TransactionMode))
			.setAutoCommit(Not bCommit)
		End If
	End With

	bSet = True

Finally:
	SetTransactionMode = bSet
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
Catch_ReadOnly:
	ScriptForge.SF_Exception.RaiseFatal(DBREADONLYERROR)
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database.SetTransactionMode

REM =========================================================== PRIVATE FUNCTIONS

REM -----------------------------------------------------------------------------
Public Function _AddToDatasets(ByRef poDataset As Object) As Long
&apos;&apos;&apos;	Insert a newly created Dataset instance in the open datasets array
&apos;&apos;&apos;	and return the index of the used entry.
&apos;&apos;&apos;	Empty space is reused.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		poDataset: the dataset instance to insert

Dim lIndex As Long			&apos;	Return value
Dim lSize As Long			&apos;	UBound of the _datasets array
Dim i As Long

Check:
	lIndex = -1
	If IsNull(poDataset) Then Exit Function
	On Local Error GoTo Finally
	lSize = UBound(_Datasets)

Try:
	&apos;	Can an empty entry be reused ?
	For i = 0 To lSize
		If IsNull(_Datasets(i)) Then
			lIndex = i
			Exit For
		End If
	Next i

	&apos;	Resize _Datasets if no empty space
	If lIndex &lt; 0 Then
		lSize = lSize + 1
		If lSize &gt; 0 Then
			ReDim Preserve _Datasets(0 To lSize)
		Else
			ReDim _Datasets (0 To 0)
		End If
		lIndex = lSize
	End If

	&apos;	Insert new object
	Set _Datasets(lIndex) = poDataset

Finally:
	_AddToDatasets = lIndex
	Exit Function
End Function	&apos;	SFDatabases.SF_Database._AddToDatasets

REM -----------------------------------------------------------------------------
Private Sub _CloseConnection()
&apos;&apos;&apos;	Close the actual connection
&apos;&apos;&apos;	To enable transaction modes, it is necessary to reinitialize the connection to the datasource.
&apos;&apos;&apos;	The reinit includes
&apos;&apos;&apos;		- the closure of all open datasets
&apos;&apos;&apos;		- flushing the buffered and committed updates
&apos;&apos;&apos;		- the effective closure
&apos;&apos;&apos;	Otherwise the experience showed undesired side-effects.

Dim oParent As Object			&apos;	Parent of actual connection
Dim oDataset As Object			&apos;	Single dataset in the _Datasets array
Dim oSession As Object			:	Set oSession = ScriptForge.SF_Session
Dim i As Long

	On Local Error GoTo Finally		&apos;	Never abort

Check:
	If IsNull(_Connection) Then Exit Sub

Try:
	&apos;	Close datasets
	For i = 0 To UBound(_Datasets)
		Set oDataset = _Datasets(i)
		If Not IsNull(oDataset) Then oDataset.CloseDataset()
		Set _Datasets(i) = Nothing
	Next i
	_Datasets = Array()

	&apos;	Flush buffers
	_FlushConnection()

	&apos;	Close the connection
	_Connection.close()
	_Connection.dispose()

Finally:
	On Local Error GoTo 0
	Exit Sub
End Sub			&apos;	SFDatabases.SF_Database._CloseConnection

REM -----------------------------------------------------------------------------
Private Function _CollectFormDocuments(ByRef poContainer As Object) As String
&apos;&apos;&apos;	Returns a token-separated string of all hierarchical formdocument names
&apos;&apos;&apos;	depending on the formdocuments container in argument
&apos;&apos;&apos;	The function traverses recursively the whole tree below the container
&apos;&apos;&apos;	The initial call starts from the container _Component.getFormDocuments
&apos;&apos;&apos;	The list contains closed and open forms

Dim sCollectNames As String			&apos;	Return value
Dim oSubItem As Object				&apos;	com.sun.star.container.XNameAccess (folder) or com.sun.star.ucb.XContent (form)
Dim i As Long
Const cstFormType = &quot;application/vnd.oasis.opendocument.text&quot;
									&apos;	Identifies forms. Folders have a zero-length content type

	On Local Error GoTo Finally

Try:
	sCollectNames = &quot;&quot;
	With poContainer
		For i = 0 To .Count - 1
			Set oSubItem = .getByIndex(i)
			If oSubItem.ContentType = cstFormType Then		&apos;	Add the form to the list
				sCollectNames = sCollectNames &amp; cstToken &amp; oSubItem.HierarchicalName
			Else
				sCollectNames = sCollectNames &amp; cstToken &amp; _CollectFormDocuments(oSubItem)
			End If
		Next i
	End With

Finally:
	If Len(sCollectNames) &gt; 0 Then
		_CollectFormDocuments = Mid(sCollectNames, Len(cstToken) + 1)	&apos;	Skip the initial token
	Else
		_CollectFormDocuments = &quot;&quot;
	End If
	Exit Function
End Function	&apos;	SFDatabases.SF_Database._CollectFormDocuments

REM -----------------------------------------------------------------------------------------------------------------------
Private Function _DFunction(ByVal psFunction As String _
								, Optional ByVal pvExpression As Variant _
								, Optional ByVal pvTableName As Variant _
								, Optional ByVal pvCriteria As Variant _
								, Optional ByVal pvOrderClause As Variant _
								) As Variant
&apos;&apos;&apos;	Build and execute a SQL statement computing the aggregate function psFunction
&apos;&apos;&apos;	on a field or expression pvExpression belonging to a table pvTableName
&apos;&apos;&apos;	filtered by a WHERE-clause pvCriteria.
&apos;&apos;&apos;	To order the results, a pvOrderClause may be precised.
&apos;&apos;&apos;	Only the 1st record will be retained anyway.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psFunction		an optional aggregate function: SUM, COUNT, AVG, LOOKUP
&apos;&apos;&apos;		pvExpression:	an SQL expression
&apos;&apos;&apos;		pvTableName:	the name of a table, NOT surrounded with quoting char
&apos;&apos;&apos;		pvCriteria:		an optional WHERE clause without the word WHERE
&apos;&apos;&apos;		pvOrderClause:	an optional order clause incl. &quot;DESC&quot; if relevant
&apos;&apos;&apos;							(meaningful only for LOOKUP)

Dim vResult As Variant			&apos;	Return value
Dim oResult As Object			&apos;	com.sun.star.sdbc.XResultSet
Dim sSql As String				&apos;	SQL statement.
Dim sExpr As String				&apos;	For inclusion of aggregate function
Dim sTarget as String			&apos;	Alias of pvExpression
Dim sWhere As String			&apos;	Alias of pvCriteria
Dim sOrderBy As String			&apos;	Alias of pvOrderClause
Dim sLimit As String			&apos;	TOP 1 clause
Dim sProductName As String		&apos;	RDBMS as a string
Const cstAliasField = &quot;[&quot; &amp; &quot;TMP_ALIAS_ANY_FIELD&quot; &amp; &quot;]&quot;		&apos;	Alias field in SQL expression
Dim cstThisSub As String		:	cstThisSub = &quot;SFDatabases.SF_Database.D&quot; &amp; psFunction
Const cstSubArgs = &quot;Expression, TableName, [Criteria=&quot;&quot;&quot;&quot;], [OrderClause=&quot;&quot;&quot;&quot;]&quot;
Const cstLookup = &quot;Lookup&quot;

	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	vResult = Null

Check:
	If IsMissing(pvCriteria) Or IsEmpty(pvCriteria) Then pvCriteria = &quot;&quot;
	If IsMissing(pvOrderClause) Or IsEmpty(pvOrderClause) Then pvOrderClause = &quot;&quot;
	If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not ScriptForge.SF_Utils._Validate(pvExpression, &quot;Expression&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(pvTableName, &quot;TableName&quot;, V_STRING, Tables, True) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(pvCriteria, &quot;Criteria&quot;, V_STRING) Then GoTo Finally
		If Not ScriptForge.SF_Utils._Validate(pvOrderClause, &quot;OrderClause&quot;, V_STRING) Then GoTo Finally
	End If

Try:
	If pvCriteria &lt;&gt; &quot;&quot; Then sWhere = &quot; WHERE &quot; &amp; pvCriteria Else sWhere = &quot;&quot;
	If pvOrderClause &lt;&gt; &quot;&quot; Then sOrderBy = &quot; ORDER BY &quot; &amp; pvOrderClause Else sOrderBy = &quot;&quot;
	sLimit = &quot;&quot;

	pvTableName = &quot;[&quot; &amp; pvTableName &amp; &quot;]&quot;

	sProductName = UCase(_MetaData.getDatabaseProductName())

	Select Case sProductName
		Case &quot;MYSQL&quot;, &quot;SQLITE&quot;
			If psFunction = cstLookup Then
				sTarget = pvExpression
				sLimit = &quot; LIMIT 1&quot;
			Else
				sTarget = UCase(psFunction) &amp; &quot;(&quot; &amp; pvExpression &amp; &quot;)&quot;
			End If
			sSql = &quot;SELECT &quot; &amp; sTarget &amp; &quot; AS &quot; &amp; cstAliasField &amp; &quot; FROM &quot; &amp; psTableName &amp;  sWhere &amp; sOrderBy &amp; sLimit
		Case &quot;FIREBIRD (ENGINE12)&quot;
			If psFunction = cstLookup Then sTarget = &quot;FIRST 1 &quot; &amp; pvExpression Else sTarget = UCase(psFunction) &amp; &quot;(&quot; &amp; pvExpression &amp; &quot;)&quot;
			sSql = &quot;SELECT &quot; &amp; sTarget &amp; &quot; AS &quot; &amp; cstAliasField &amp; &quot; FROM &quot; &amp; pvTableName &amp; sWhere &amp; sOrderBy
		Case Else		&apos;	Standard syntax - Includes HSQLDB
			If psFunction = cstLookup Then sTarget = &quot;TOP 1 &quot; &amp; pvExpression Else sTarget = UCase(psFunction) &amp; &quot;(&quot; &amp; pvExpression &amp; &quot;)&quot;
			sSql = &quot;SELECT &quot; &amp; sTarget &amp; &quot; AS &quot; &amp; cstAliasField &amp; &quot; FROM &quot; &amp; pvTableName &amp; sWhere &amp; sOrderBy
	End Select

	&apos;	Execute the SQL statement and retain the first column of the first record
	Set oResult = _ExecuteSql(sSql, True)
	If Not IsNull(oResult) And Not IsEmpty(oResult) Then
		If oResult.first() Then vResult = _GetColumnValue(oResult, 1) Else GoTo Finally
	End If
	Set oResult = Nothing

Finally:
	_DFunction = vResult
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function		&apos;	SFDatabases.SF_Database._DFunction

REM -----------------------------------------------------------------------------
Private Function _ExecuteSql(ByVal psSql As String _
								, ByVal pbDirect As Boolean _
								) As Variant
&apos;&apos;&apos;	Return a read-only Resultset based on a SELECT SQL statement or execute the given action SQL (INSERT, CREATE TABLE, ...)
&apos;&apos;&apos;	The method raises a fatal error when the SQL statement cannot be interpreted
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psSql : the SQL statement. Square brackets are replaced by the correct field surrounding character
&apos;&apos;&apos;		pbDirect: when True, no syntax conversion is done by LO. Default = False
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		SQLSYNTAXERROR			The given SQL statement is incorrect

Dim vResult As Variant			&apos;	Return value - com.sun.star.sdbc.XResultSet or Boolean
Dim oStatement As Object		&apos;	com.sun.star.sdbc.XStatement
Dim sSql As String				&apos;	Alias of psSql
Dim bSelect As Boolean			&apos;	True when SELECT statement
Dim bErrorHandler As Boolean	&apos;	Can be set off to ease debugging of complex SQL statements

	Set vResult = Nothing
	bErrorHandler = ScriptForge.SF_Utils._ErrorHandling()
	If bErrorHandler Then On Local Error GoTo Catch
    
Try:
	sSql = _ReplaceSquareBrackets(psSql)
	bSelect = ScriptForge.SF_String.StartsWith(sSql, &quot;SELECT&quot;, CaseSensitive := False)

	Set oStatement = _Connection.createStatement()
	With oStatement
		If bSelect Then
			.ResultSetType = com.sun.star.sdbc.ResultSetType.SCROLL_INSENSITIVE
			.ResultSetConcurrency = com.sun.star.sdbc.ResultSetConcurrency.READ_ONLY
		End If
		.EscapeProcessing = Not pbDirect

		&apos;	Setup the result set
		If bErrorHandler Then On Local Error GoTo Catch_Sql
		If bSelect Then Set vResult = .executeQuery(sSql) Else vResult = .execute(sSql)
	End With

Finally:
	_ExecuteSql = vResult
	Set oStatement = Nothing
	Exit Function
Catch_Sql:
	On Local Error GoTo 0
	ScriptForge.SF_Exception.RaiseFatal(SQLSYNTAXERROR, sSql)
	GoTo Finally
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database._ExecuteSql

REM -----------------------------------------------------------------------------
Private Sub _FlushConnection()
&apos;&apos;&apos;	Empties the buffers of the actual connection
&apos;&apos;&apos;	Sub called after each commit and at connection closure..

Dim oParent As Object			&apos;	Parent of actual connection
Dim oSession As Object			:	Set oSession = ScriptForge.SF_Session

	On Local Error GoTo Finally		&apos;	Never abort

Check:
	If IsNull(_Connection) Then Exit Sub

Try:
	&apos;	Flush buffers
	With oSession
		If .HasUnoMethod(_Connection, &quot;getParent&quot;) Then
			Set oParent = _Connection.getParent()
			If .HasUnoMethod(oParent, &quot;flush&quot;) Then oParent.flush()
		ElseIf .HasUnoMethod(_Connection, &quot;flush&quot;) Then
			_Connection.flush()
		End If
	End With

Finally:
	Exit Sub
End Sub			&apos;	SFDatabases.SF_Database._FlushConnection

REM -----------------------------------------------------------------------------
Private Function _GetColumnValue(ByRef poResultSet As Object _
									, ByVal plColIndex As Long _
									) As Variant
&apos;&apos;&apos;	Get the data stored in the current record of a result set in a given column
&apos;&apos;&apos;	The type of the column is found in the resultset&apos;s metadata
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		poResultSet: com.sun.star.sdbc.XResultSet or com.sun.star.awt.XTabControllerModel
&apos;&apos;&apos;		plColIndex: the index of the column to extract the value from. Starts at 1
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The Variant value found in the column
&apos;&apos;&apos;		Dates and times are returned as Basic dates
&apos;&apos;&apos;		Null values are returned as Null
&apos;&apos;&apos;		Binary fields are returned as a Long giving their length
&apos;&apos;&apos;		Errors or strange data types are returned as Null as well
	
Dim vValue As Variant			&apos;	Return value
Dim lType As Long				&apos;	SQL column type: com.sun.star.sdbc.DataType
Dim vDateTime As Variant		&apos;	com.sun.star.util.DateTime
Dim oStream As Object			&apos;	Long character or binary streams
Dim bNullable As Boolean		&apos;	The field is defined as accepting Null values
Dim lSize As Long				&apos;	Binary field length

Const cstMaxBinlength = 2 * 65535

	On Local Error Goto 0			&apos;	Disable error handler
	vValue = Empty					&apos;	Default value if error

	With com.sun.star.sdbc.DataType
		lType = poResultSet.MetaData.getColumnType(plColIndex)
		bNullable = ( poResultSet.MetaData.IsNullable(plColIndex) = com.sun.star.sdbc.ColumnValue.NULLABLE )

		Select Case lType
			Case .ARRAY						:	vValue = poResultSet.getArray(plColIndex)
			Case .BINARY, .VARBINARY, .LONGVARBINARY, .BLOB
				Set oStream = poResultSet.getBinaryStream(plColIndex)
				If IsNull(oStream) Then
					lSize = 0
				Else
					If bNullable Then
						If Not poResultSet.wasNull() Then lSize = CLng(oStream.getLength()) Else lSize = 0
					Else
						lSize = CLng(oStream.getLength())
					End If
					oStream.closeInput()
				End If
				vValue = lSize	&apos;	Return length of field, not content
			Case .BIT, .BOOLEAN				:	vValue = poResultSet.getBoolean(plColIndex)
			Case .DATE
				vDateTime = poResultSet.getDate(plColIndex)
				If Not poResultSet.wasNull() Then vValue = DateSerial(CInt(vDateTime.Year), CInt(vDateTime.Month), CInt(vDateTime.Day))
			Case .DISTINCT, .OBJECT, .OTHER, .STRUCT
				vValue = Null
			Case .DOUBLE, .REAL				:	vValue = poResultSet.getDouble(plColIndex)
			Case .FLOAT						:	vValue = poResultSet.getFloat(plColIndex)
			Case .INTEGER, .SMALLINT		:	vValue = poResultSet.getInt(plColIndex)
			Case .BIGINT					:	vValue = CLng(poResultSet.getLong(plColIndex))
			Case .DECIMAL, .NUMERIC			:	vValue = poResultSet.getDouble(plColIndex)
			Case .SQLNULL					:	vValue = poResultSet.getNull(plColIndex)
			Case .REF						:	vValue = poResultSet.getRef(plColIndex)
			Case .TINYINT					:	vValue = poResultSet.getShort(plColIndex)
			Case .CHAR, .VARCHAR, .LONGVARCHAR, .CLOB
												vValue = poResultSet.getString(plColIndex)
			Case .TIME
				vDateTime = poResultSet.getTime(plColIndex)
				If Not poResultSet.wasNull() Then vValue = TimeSerial(vDateTime.Hours, vDateTime.Minutes, vDateTime.Seconds)&apos;, vDateTime.HundredthSeconds)
			Case .TIMESTAMP
				vDateTime = poResultSet.getTimeStamp(plColIndex)
				If Not poResultSet.wasNull() Then vValue = DateSerial(CInt(vDateTime.Year), CInt(vDateTime.Month), CInt(vDateTime.Day)) _
						+ TimeSerial(vDateTime.Hours, vDateTime.Minutes, vDateTime.Seconds)&apos;, vDateTime.HundredthSeconds)
			Case Else
				vValue = poResultSet.getString(plColIndex)			&apos;GIVE STRING A TRY
				If IsNumeric(vValue) Then vValue = Val(vValue)		&apos;Required when type = &quot;&quot;, sometimes numeric fields are returned as strings (query/MSAccess)
		End Select
		&apos;	.wasNull() must be preceded by getXXX(). Done. Test for Null here.
		If bNullable Then
			If poResultSet.wasNull() Then vValue = Null
		End If
	End With
	
	_GetColumnValue = vValue
	
End Function	&apos;	SFDatabases.SF_Database._GetColumnValue

REM -----------------------------------------------------------------------------
Public Function _OpenDatasheet(Optional ByVal psCommand As Variant _
								, piDatasheetType As Integer _
								, pbEscapeProcessing As Boolean _
								) As Object
&apos;&apos;&apos;	Open the datasheet given by its name and its type
&apos;&apos;&apos;	The datasheet will live independently from any other component
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psCommand: a valid table or query name or an SQL statement as a case-sensitive string
&apos;&apos;&apos;		piDatasheetType: one of the com.sun.star.sdb.CommandType constants
&apos;&apos;&apos;		pbEscapeProcessing: == Not DirectSql
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		A Datasheet class instance if the datasheet could be opened, otherwise Nothing

Dim oOpen As Object				&apos;	Return value
Dim oNewDatasheet As Object		&apos;	com.sun.star.lang.XComponent
Dim oURL As Object				&apos;	com.sun.star.util.URL
Dim oDispatch As Object			&apos;	com.sun.star.frame.XDispatch
Dim vArgs As Variant			&apos;	Array of property values

	On Local Error GoTo Catch
	Set oOpen = Nothing

Try:
	&apos;	Setup the dispatcher
	Set oURL = New com.sun.star.util.URL
	oURL.Complete = &quot;.component:DB/DataSourceBrowser&quot;
	Set oDispatch = StarDesktop.queryDispatch(oURL, &quot;_blank&quot;, com.sun.star.frame.FrameSearchFlag.CREATE)

	&apos;	Setup the arguments of the component to create
	With ScriptForge.SF_Utils
		vArgs = Array( _
			._MakePropertyValue(&quot;ActiveConnection&quot;,		_Connection) _
			, ._MakePropertyValue(&quot;CommandType&quot;,		piDatasheetType) _
			, ._MakePropertyValue(&quot;Command&quot;,			psCommand) _
			, ._MakePropertyValue(&quot;ShowMenu&quot;,			True) _
			, ._MakePropertyValue(&quot;ShowTreeView&quot;,		False) _
			, ._MakePropertyValue(&quot;ShowTreeViewButton&quot;,	False) _
			, ._MakePropertyValue(&quot;Filter&quot;,				&quot;&quot;) _
			, ._MakePropertyValue(&quot;ApplyFilter&quot;,		False) _
			, ._MakePropertyValue(&quot;EscapeProcessing&quot;,	pbEscapeProcessing) _
			)
	End With

	&apos;	Open the targeted datasheet
	Set oNewDatasheet = oDispatch.dispatchWithReturnValue(oURL, vArgs)
	If Not IsNull(oNewDatasheet) Then Set oOpen = ScriptForge.SF_Services.CreateScriptService(&quot;SFDatabases.Datasheet&quot;, oNewDatasheet, [Me])

Finally:
	Set _OpenDatasheet = oOpen
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDocuments.SF_Base._OpenDatasheet

REM -----------------------------------------------------------------------------
Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant
&apos;&apos;&apos;	Return the value of the named property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psProperty: the name of the property

Dim cstThisSub As String
Const cstSubArgs = &quot;&quot;

	cstThisSub = &quot;SFDatabases.Database.get&quot; &amp; psProperty
	If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

	ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

	Select Case psProperty
		Case &quot;Queries&quot;
			If Not IsNull(_Connection) Then _PropertyGet = _Connection.Queries.getElementNames() Else _PropertyGet = Array()
		Case &quot;Tables&quot;
			If Not IsNull(_Connection) Then _PropertyGet = _Connection.Tables.getElementNames() Else _PropertyGet = Array()
		Case &quot;XConnection&quot;
			Set _PropertyGet = _Connection
		Case &quot;XMetaData&quot;
			Set _PropertyGet = _MetaData
		Case Else
			_PropertyGet = Null
	End Select

Finally:
	ScriptForge.SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	SFDatabases.SF_Database._PropertyGet

REM -----------------------------------------------------------------------------
Private Function _ReplaceSquareBrackets(ByVal psSql As String) As String
&apos;&apos;&apos;	Returns the input SQL command after replacement of square brackets by the table/field names quoting character

Dim sSql As String				&apos;	Return value
Dim sQuote As String			&apos;	RDBMS specific table/field surrounding character
Dim sConstQuote As String		&apos;	Delimiter for string constants in SQL - usually the single quote
Const cstDouble = &quot;&quot;&quot;&quot;	:	Const cstSingle = &quot;&apos;&quot;

Try:
	sQuote = _MetaData.IdentifierQuoteString
	sConstQuote = Iif(sQuote = cstSingle, cstDouble, cstSingle)

	&apos;	Replace the square brackets
	sSql = Join(ScriptForge.SF_String.SplitNotQuoted(psSql, &quot;[&quot;, , sConstQuote), sQuote)
	sSql = Join(ScriptForge.SF_String.SplitNotQuoted(sSql, &quot;]&quot;, , sConstQuote), sQuote)

Finally:
	_ReplaceSquareBrackets = sSql
	Exit Function
End Function	&apos;	SFDatabases.SF_Database._ReplaceSquareBrackets

REM -----------------------------------------------------------------------------
Private Function _Repr() As String
&apos;&apos;&apos;	Convert the Database instance to a readable string, typically for debugging purposes (DebugPrint ...)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Return:
&apos;&apos;&apos;		&quot;[DATABASE]: Location (Statusbar)&quot;

	_Repr = &quot;[DATABASE]: &quot; &amp; _Location &amp; &quot; (&quot; &amp; _URL &amp; &quot;)&quot;

End Function	&apos;	SFDatabases.SF_Database._Repr

REM ============================================ END OF SFDATABASES.SF_DATABASE
</script:module>