Radicore Forum
Fast Uncompromising Discussions. FUDforum will get your users talking.

Home » RADICORE » RADICORE Installation Issues » SQLSRV Driver (How to setup config.inc for multi database option with one of them being 'sqlsrv' )
SQLSRV Driver [message #4722] Sat, 09 May 2015 11:44 Go to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
I am like a chicken with my head cut-off trying to install and configure a SQLSRV driver on Windows 7 (in a multi database configuration).

I am on Windows 7. I used the XAMPP install packages. My main database is MySQL to handle audit, dict, menu, and workflow databases. So in my config.inc file 'dbms' = 'mysql'. That is working. I have fixed my include_path, by hard-coding it into C:\xampp\php\config\php.ini. (I wasn't able to get Apache to load my htaccess.txt files.)

I have 3 variations working in Administrative Tools >> ODBC:
ODBC Driver 11 for SQL Server
SQL Server
SQL Server Native Client 11.0

In my php extensions folder (C:\xampp\php\ext) I installed the SQL Server drivers:
php_sqlsrv_54_ts.dll
php_sqlsrv_54_nts.dll
...
php_sqlsrv_56_ts.dll
...

In php.ini, I have loaded this last one, using:
extension=php_sqlsrv_56_ts.dll

In my config.inc, I have tried dozens of permutations for multi database configuration.

(I will post the config.inc details in a separate post.)
Re: SQLSRV Driver [message #4723 is a reply to message #4722] Sat, 09 May 2015 12:24 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
Have you followed the instructions at http://www.tonymarston.net/php-mysql/infrastructure-faq.html #faq92 ?

Re: SQLSRV Driver [message #4725 is a reply to message #4723] Mon, 11 May 2015 02:53 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Tony! Thanks for replying. I am a huge fan. Thank you, so much, for putting this project together and out for the public. The documentation is very helpful. I appreciate all of your insightful explanations about your design decisions, and critique of other more bloated alternatives, (with cutting comments for entertainment value). I am hoping to get deeper into it, so I can modify it for my project.

My project is to provide a script that can extract, transform, and load (ETL) to and from the production MSSQL2008 DB to a newly redesigned schema running on Linux/Postrgres 9.4, in both directions--easing the transition to the new database by allowing synchronization to the old database until all of the dependent services can be migrated to the new. I do not need the web UI components, but the import database (from both pgsql and sqlsrv), and then export PHP code part is what I am after, so I can use the classes.

My current sticking point is when I go to "Dictionary >> Databases >> Import Database". I am using the localhost/mysql db to store audit, dict, menu, and workflow. This works. So now I want to connect to two other databases, MSSQL2008, and PGSQL, in order to import them. I assumed that I would just add the two extra dbs into the '$servers' array. I started by just trying to add the MSQL2008 DB to the servers array, but then I get an error, "Fatal Error: Entry missing for database 'audit' in SERVERS array in CONFIG file (# 256)". So, then it seems that the mysql connection needs to be in $servers array as well. (I left out the pgsql db for the time being.) So, when I add localhost/mysql db as the first entry (see below config) I am getting a driver error, below when trying to import MSSQL2008 db. (A capture using wireshark shows that there is no TCP connection even attempted by the localhost to the MSSQL2008 remote host, 10.100.150.86, despite the warning below). (I tried both IPADDR and DNS hostname.)

Here is my warning, via webpage:
Fatal Error: [Microsoft][ODBC Driver 11 for SQL Server]Named Pipes Provider: Could not open a connection to SQL Server [2].
[Microsoft][ODBC Driver 11 for SQL Server]Login timeout expired
[Microsoft][ODBC Driver 11 for SQL Server]A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online. (# 2)
Error in line 550 of file 'C:\xampp\radicore\includes\dml.sqlsrv.class.inc'.
PHP_SELF: /radicore/dict/database(add1)import.php
CURRENT DIRECTORY: C:\xampp\htdocs\radicore\dict
SERVER_ADDR: ::1
SERVER_NAME: localhost
HTTP_HOST: localhost
User Id: MGR
Role Id: GLOBAL
REMOTE_ADDR: ::1
REQUEST_URI: /radicore/dict/database(add1)import.php?session_name=menu21


Actually, I am not sure which driver is the one I should be connecting to. Right now, I am loading in php.ini these two:

php.ini
---------
extension=php_sqlsrv_56_ts.dll
extension=php_pdo_sqlsrv_56_ts.dll

In addition to the two DLLs listed above, I have the following drivers installed, and all seem to work when testing via the Admin Tools >> ODBC Data Source Admin tool.
ODBC Driver 11 for SQL Server (MSODBCSQL11.DLL)
SQL Server (SQLSRV32.DLL)
SQL Server Native Client 11.0 (SQLNCLI11.DLL)

From command line, I can see that 'sqlsrv' is loaded.

I am not sure what to make of the sqlsrv-specific fields. I tried adding them and using special 'User DSN' and 'System DSN' names for serverName, as well as using the servers proper DNS name, but it didn't help:

$servers[1]['serverName'] = 'MYUSERDSNNAME';
$servers[1]['SQLSRV_schema'] = 'dbo';
$servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
'ReturnDatesAsStrings' => true)

Here is my config.inc:

<?php
// ************************************************************ *****************
// Copyright 2003-2005 by A J Marston <http://www.tonymarston.net>
// Copyright 2006-2014 by Radicore Software Limited <http://www.radicore.org>
// ************************************************************ *****************

// This file contains database access details and values for optional constants/globals
$GLOBALS['dbms'] = 'mysql'; // database engine is MySQL
//$GLOBALS['dbms'] = 'pgsql'; // database engine is PostgreSQL
//$GLOBALS['dbms'] = 'oracle'; // database engine is Oracle
//$GLOBALS['dbms'] = 'sqlsrv'; // database engine is SQL Server (Microsoft)

if ($GLOBALS['dbms'] == 'oracle') {
$GLOBALS['dbhost'] = '//localhost/xe';

} elseif ($GLOBALS['dbms'] == 'sqlsrv') {
// JOIN clauses in SQL Server require 'dbname.<schema>.tblname'
$GLOBALS['SQLSRV_schema'] = 'dbo';

} elseif ($GLOBALS['dbms'] == 'pgsql') {
// NOTE: with MYSQL there are tables withing databases, and within a single connection
// it is possible to access tables in any database.
// But with PostgreSQL there are tables within schemas within databases, and within
// a single connection it is only possible to access a single database, but any number
// of schemas within that database.
// When using PostgreSQL you must supply a value for $PGSQL_dbname for the single database
// connection, and what is known as 'dbname' to MySQL becomes 'schema' to PostgreSQL.
$GLOBALS['dbhost'] = 'localhost';
$GLOBALS['PGSQL_dbname'] = 'radicore';

} else {
$GLOBALS['dbhost'] = 'localhost';
} // if

// NOTE: $dbprefix is for my web host (shared) where my databases are prefixed
// with my account name to keep them separate from other accounts.

if (preg_match('/^(127.0.0.1|localhost|desktop|laptop)$/i', $_SERVER['SERVER_NAME'])) { // this is for my PC
if ($GLOBALS['dbms'] == "sqlsrv") {
$GLOBALS['serverName'] = '(local)';
$GLOBALS['connectionInfo'] = array('CharacterSet' => 'UTF-8',
'ReturnDatesAsStrings' => true);
$GLOBALS['SQLSRV_schema'] = 'dbo';
} else {
$GLOBALS['dbusername'] = 'root';
$GLOBALS['dbuserpass'] = '';
$GLOBALS['dbprefix'] = null;
$GLOBALS['dbport'] = null;
$GLOBALS['dbsocket'] = null;
} // if
// set these only if secure HTTPS protocol is available on your server
$GLOBALS['http_server'] = '';
$GLOBALS['https_server'] = '';
$GLOBALS['https_server_suffix'] = '';
} else {
// this is for my shared web host
$GLOBALS['dbusername'] = '****';
$GLOBALS['dbuserpass'] = '****';
$GLOBALS['dbprefix'] = '****';
$GLOBALS['dbport'] = null;
$GLOBALS['dbsocket'] = null;
// set these only if secure HTTPS protocol is available on your server
$GLOBALS['http_server'] = '';
$GLOBALS['https_server'] = '';
$GLOBALS['https_server_suffix'] = '';
} // if

// this demonstrates the multi-server option (see FAQ92)
if (preg_match('/^(127.0.0.1|localhost|desktop|laptop)$/i', $_SERVER['SERVER_NAME'])) {
global $servers;
// server 0
$servers[0]['dbhost'] = 'localhost';
$servers[0]['dbengine'] = 'mysql';
$servers[0]['dbusername'] = 'root';
$servers[0]['dbuserpass'] = '';
$servers[0]['dbport'] = '';
$servers[0]['dbsocket'] = '';
$servers[0]['dbprefix'] = '';
$servers[0]['dbnames'] = '*';
$servers[0]['ssl_key'] = '';
$servers[0]['ssl_cert'] = '';
$servers[0]['ssl_ca'] = '';
$servers[0]['ssl_capath'] = '';
$servers[0]['ssl_cipher'] = '';
// server 1
$servers[1]['dbhost'] = '10.100.150.86';
$servers[1]['dbengine'] = 'sqlsrv';
$servers[1]['dbusername'] = 'myusername';
$servers[1]['dbuserpass'] = 'mypassword';
$servers[1]['dbport'] = '';
$servers[1]['dbsocket'] = '';
$servers[1]['dbprefix'] = '';
$servers[1]['dbnames'] = 'mydbname';
// $servers[1]['serverName'] = 'MYUSERDSNNAME';
// $servers[1]['SQLSRV_schema'] = 'dbo';
// $servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
// 'ReturnDatesAsStrings' => true);
} // if

// set this to true if you want all XSL transformations to be done by the client
// (NOTE: only valid if supported by your client browser)
$GLOBALS['XSLT_client_side'] = false;

// set this to TRUE to write all sql queries to file 'sql/<script_id>.sql' (for debugging)
$GLOBALS['log_sql_query'] = false;

// set this to TRUE to write all XML documents to file 'xsl/<script_id>.xml' (for debugging)
$GLOBALS['log_xml_document'] = false;

// set date format - choose one of the following (may be overridden on the MNU_LANGUAGE table)
$GLOBALS['date_format'] = 'dmy'; // '21 Jan 2001'
//$GLOBALS['date_format'] = 'mdy'; // 'Jan 21 2001'
//$GLOBALS['date_format'] = 'ymd'; // '2001 Jan 21'
//$GLOBALS['date_format'] = 'dd.mm.yyyy'; // '21.06.2001'
//$GLOBALS['date_format'] = 'dd/mm/yyyy'; // '21/06/2001'
//$GLOBALS['date_format'] = 'dd/mm/yy'; // '21/06/01'
//$GLOBALS['date_format'] = 'yyyy-mm-dd'; // '2013-03-02'

// use HTTPS for all web pages
$GLOBALS['use_https'] = false;

// define this only if content such as CSS or JS files are not to be referenced using HTTPS
//define('HTTPS_NOT_FOR_FILES', true);

// use to access files in the 'classes/custom-processing/<project>/' folder
//$GLOBALS['project_code'] = 'unknown';

// converts datetime between client and server timezones (PHP version >= 5.2)
//$GLOBALS['server_timezone'] = 'Europe/London';

// defines email address(es) to which any error_log will be sent
//define('ERROR_LOG_MAILTO', 'me@domain.com, someone.else@domain.co.uk');

// defines directory into which error logs will be written
define('ERROR_LOG_DIR', $_SERVER['DOCUMENT_ROOT'].'/radicore/error_logs');

// defines Return-Path to be used on all outgoing emails
//define('MAIL_RETURN_PATH', 'postmaster@domain.com');

// this causes the errorHandler to automatically activate the DBG debugger
//define('DEBUGBREAK_ON_ERROR', true);

// causes the logstuff() function to write details to the log file
//define('LOGSTUFF', true);

// causes the database session handler to be ignored
//define('NO_SESSION_HANDLER', true);

?>




Re: SQLSRV Driver [message #4727 is a reply to message #4725] Mon, 11 May 2015 04:21 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
I can see two problems:

(1) You are not following the instructions at http://www.tonymarston.net/php-mysql/infrastructure-faq.html #faq92 which clearly states that when you are using the multi-server option it is only the last entry which should have dbnames set to '*' as this means "any database not identified in the previous entries". So the first entry should contain the following:
$servers[0]['dbnames'] = 'menu, audit, workflow, dict';

Each entry before the last one MUST identify the names of the databases which can be found on that server.

(2) You are not following the instructions at http://www.tonymarston.net/php-mysql/sqlsrv.html#config.inc which states that the server name should be set to "(local)" as in the following:
$servers[1]['dbengine']       = 'sqlsrv';
$servers[1]['serverName']     = '(local)';
$servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
                                      'ReturnDatesAsStrings' => true);
$servers[1]['SQLSRV_schema']  = 'dbo';


Note: when you are using the $servers array you do not need to use the $GLOBALS array. Use one or the other, but not both.


Re: SQLSRV Driver [message #4728 is a reply to message #4723] Mon, 11 May 2015 08:12 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Same error as previously:
Fatal Error: [Microsoft][ODBC Driver 11 for SQL Server]Named Pipes Provider: Could not open a connection to SQL Server [53].
...


// this demonstrates the multi-server option (see FAQ92)
if (preg_match('/^(127.0.0.1|localhost|desktop|laptop)$/i', $_SERVER['SERVER_NAME'])) {
global $servers;
// server 0
$servers[0]['dbhost'] = 'localhost';
$servers[0]['dbengine'] = 'mysql';
$servers[0]['dbusername'] = 'root';
$servers[0]['dbuserpass'] = '';
$servers[0]['dbport'] = '';
$servers[0]['dbsocket'] = '';
$servers[0]['dbprefix'] = '';
$servers[0]['dbnames'] = 'audit,dict,menu,workflow';
$servers[0]['ssl_key'] = '';
$servers[0]['ssl_cert'] = '';
$servers[0]['ssl_ca'] = '';
$servers[0]['ssl_capath'] = '';
$servers[0]['ssl_cipher'] = '';
// server 1
$servers[1]['dbhost'] = '10.100.150.86';
$servers[1]['dbengine'] = 'sqlsrv';
$servers[1]['dbusername'] = 'mydbusername';
$servers[1]['dbuserpass'] = 'mydbpassword';
$servers[1]['dbport'] = '';
$servers[1]['dbsocket'] = '';
$servers[1]['dbprefix'] = '';
$servers[1]['dbnames'] = 'mydbname,*';
$servers[1]['serverName'] = '(local)';
$servers[1]['SQLSRV_schema'] = 'dbo';
$servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
'ReturnDatesAsStrings' => true);
} // if
Re: SQLSRV Driver [message #4729 is a reply to message #4728] Mon, 11 May 2015 09:26 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
Can you access the database using SQL Server Management Studio?

You should also try searching for this error in your search engine. You will find a lot of references such as this one - http://blog.sqlauthority.com/2009/05/21/sql-server-fix-error -provider-named-pipes-provider-error-40-could-not-open-a-con nection-to-sql-server-microsoft-sql-server-error/


Re: SQLSRV Driver [message #4730 is a reply to message #4727] Mon, 11 May 2015 09:35 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
The following worked:

// server 1
$servers[1]['dbhost'] = 'my.fqdn.com';
//$servers[1]['dbhost'] = '10.100.150.86';
$servers[1]['dbengine'] = 'sqlsrv';
$servers[1]['dbusername'] = 'myuser';
$servers[1]['dbuserpass'] = 'mypwd';
$servers[1]['dbport'] = '';
$servers[1]['dbsocket'] = '';
$servers[1]['dbprefix'] = '';
$servers[1]['dbnames'] = 'idp,*';
$servers[1]['serverName'] = 'my.fqdn.com';
$servers[1]['SQLSRV_schema'] = 'dbo';
$servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
'UID' => 'myuser',
'PWD' => 'mypwd',
'ReturnDatesAsStrings' => true);
Re: SQLSRV Driver [message #4731 is a reply to message #4729] Mon, 11 May 2015 09:39 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Thanks for the pointers.
Re: SQLSRV Driver [message #4732 is a reply to message #4731] Mon, 11 May 2015 09:40 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
Glad to help.

Re: SQLSRV Driver [message #4733 is a reply to message #4729] Mon, 11 May 2015 16:47 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Probably obvious of a more experienced troubleshooter, but for the benefit of others, here is the test.php file that I used to check my connection issue:


<?php

// server 1
$servers[1]['dbhost'] = 'my.fqdn.com';
$servers[1]['dbengine'] = 'sqlsrv';
$servers[1]['dbusername'] = 'myusername';
$servers[1]['dbuserpass'] = 'mypassword';
$servers[1]['dbport'] = '';
$servers[1]['dbsocket'] = '';
$servers[1]['dbprefix'] = '';
$servers[1]['dbnames'] = 'MYDBNAME,*';
$servers[1]['serverName'] = 'my.fqdn.com';
$servers[1]['SQLSRV_schema'] = 'dbo';
$servers[1]['connectionInfo'] = array(
'Database' => 'MYDBNAME',
'UID' => 'myusername',
'PWD' => 'mypassword',
'CharacterSet' => 'UTF-8',
'ReturnDatesAsStrings' => true);

$dbconn = sqlsrv_connect($servers[1]['serverName'], $servers[1]['connectionInfo']) or
die( print_r( sqlsrv_errors(), true));

?
Re: SQLSRV Driver [message #4735 is a reply to message #4732] Tue, 12 May 2015 04:31 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Still having an issue; this time it is when trying to add a remote pgsql db to the servers array from this config.inc:

// server 0
$servers[0]['dbhost'] = 'my.fqdn.com';
//$servers[0]['dbhost'] = '10.206.29.212';
$servers[0]['dbengine'] = 'pgsql';
$servers[0]['dbusername'] = 'mydbuser';
$servers[0]['dbuserpass'] = 'mydbpwd';
$servers[0]['dbport'] = '5432';
$servers[0]['dbsocket'] = '';
$servers[0]['dbprefix'] = '';
$servers[0]['PGSQL_dbname'] = 'radicore';
$servers[0]['dbnames'] = 'newscheme,*';
$servers[0]['ssl_key'] = '';
$servers[0]['ssl_cert'] = '';
$servers[0]['ssl_ca'] = '';
$servers[0]['ssl_capath'] = '';
$servers[0]['ssl_cipher'] = '';
// server


Error:
Fatal Error: Undefined variable: unbuffered_query
Error in line 77 of file 'C:\xampp\radicore\includes\dml.pgsql.class.inc'.
PHP_SELF: /radicore/dict/database(add1)import.php
CURRENT DIRECTORY: C:\xampp\htdocs\radicore\dict
SERVER_ADDR: ::1
SERVER_NAME: localhost
HTTP_HOST: localhost
User Id: MGR
Role Id: GLOBAL
REMOTE_ADDR: ::1
REQUEST_URI: /radicore/dict/database(add1)import.php?session_name=menu21

Line 77: trigger_error('No value supplied for PGSQL_dbname', E_USER_ERROR);

I am looking in std.table.class.inc and dict_database_s01.class.inc. Can't find any place where PGSQL_dbname is retrieved from $servers array.


Re: SQLSRV Driver [message #4736 is a reply to message #4735] Tue, 12 May 2015 05:29 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
I don't now which version of Radicore you are using, but I cannot reproduce this error in the latest version.

If you look in std.table.class.inc at line 8247 (in the _getDBMSengine() method) you will see where PGSQL_dbname is loaded into the array of arguments which is passed to the database driver.


Re: SQLSRV Driver [message #4737 is a reply to message #4736] Tue, 12 May 2015 05:34 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
radicore-2015-03-01.zip
Re: SQLSRV Driver [message #4738 is a reply to message #4736] Tue, 12 May 2015 05:40 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
Any idea why 'unbuffered_query' is undefined, and how is that related to line 77 PGSQL_dbname.
Re: SQLSRV Driver [message #4739 is a reply to message #4738] Tue, 12 May 2015 06:01 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
I suggest you try upgrading to the latest version, released on 20015-05-05, as this does not produce the errors which you describe.

Re: SQLSRV Driver [message #4740 is a reply to message #4739] Tue, 12 May 2015 06:29 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
If I hard-code 'dbname' into the constructor for pgsql($args) then I am able to connect.


function pgsql ($args=null)
{
$args['dbname'] = 'radicore'
Re: SQLSRV Driver [message #4741 is a reply to message #4740] Tue, 12 May 2015 06:43 Go to previous messageGo to next message
AJM is currently offline  AJM
Messages: 2367
Registered: April 2006
Location: Surrey, UK
Senior Member
That should not be necessary, and certainly is not necessary when I run the latest version of the code on my development PC.

Re: SQLSRV Driver [message #4742 is a reply to message #4739] Tue, 12 May 2015 06:44 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
correction: that should have been
$args['dbname'] = 'newscheme'; # radicore is PGSQL_dbname, newscheme is 'dbname' (or schema).

(I will also check out the latest release and see if that did fix it.)
Thanks again.
Re: SQLSRV Driver [message #4767 is a reply to message #4727] Wed, 03 June 2015 17:06 Go to previous messageGo to next message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
AJM wrote on Mon, 11 May 2015 04:21


(2) You are not following the instructions at http://www.tonymarston.net/php-mysql/sqlsrv.html#config.inc which states that the server name should be set to "(local)" as in the following:
$servers[1]['dbengine']       = 'sqlsrv';
$servers[1]['serverName']     = '(local)';
$servers[1]['connectionInfo'] = array('CharacterSet' => 'UTF-8',
                                      'ReturnDatesAsStrings' => true);
$servers[1]['SQLSRV_schema']  = 'dbo';


Note: when you are using the $servers array you do not need to use the $GLOBALS array. Use one or the other, but not both.


This seems to refer to localhost.
$servers[1]['serverName']     = '(local)';

Re: SQLSRV Driver [message #4768 is a reply to message #4742] Wed, 03 June 2015 17:21 Go to previous message
rafs is currently offline  rafs
Messages: 69
Registered: May 2015
Member
For 'sqlsrv' connections, 'dbhost' is not required, only 'serverName' field.
Previous Topic: Failed opening required 'include.general.inc'
Next Topic: Error: This application has encountered an unrecoverable error
Goto Forum:
  


Current Time: Thu Nov 21 07:39:06 EST 2024

Total time taken to generate the page: 0.02540 seconds