New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkp/pkp-lib#3617 Support for MS SQL #3603

Open
wants to merge 27 commits into
base: master
from
Commits
Jump to file or symbol
Failed to load files and symbols.
+18,416 −25,473
Diff settings

Always

Just for now

Next

Support for MS SQL

  • Loading branch information...
nibou230 committed Apr 11, 2018
commit ebb2a87c7beb3dfaeb601d54c37da246912e4b09
@@ -279,13 +279,14 @@ function getNumAnnouncementsByAssocId($assocType, $assocId, $numAnnouncements, $
* @return object DAOResultFactory containing matching Announcements
*/
function getAnnouncementsNotExpiredByAssocId($assocType, $assocId, $rangeInfo = null) {
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieveRange(
'SELECT *
FROM announcements
WHERE assoc_type = ?
AND assoc_id = ?
AND (date_expire IS NULL OR DATE(date_expire) > CURRENT_DATE)

This comment has been minimized.

@asmecher

asmecher Jun 14, 2018

Member

I think the ADODB $sysDate construct might be better to use?

This comment has been minimized.

@nibou230

nibou230 Jun 15, 2018

You mean generating the date with ADOdb?

This comment has been minimized.

@asmecher

asmecher Jun 19, 2018

Member

$sysDate in ADODB contains the name of the SQL function to use according to the DBMS. The date would still be generated on the SQL server.

This comment has been minimized.

@asmecher

asmecher Jun 20, 2018

Member

Replaced by f338bb4.

AND (DATE(date_posted) <= CURRENT_DATE)
AND (date_expire IS NULL OR ' . ($isSqlServer ? 'date_expire > GETDATE()' : ' DATE(date_expire) > CURRENT_DATE') . ')
AND (' . ($isSqlServer ? 'date_posted <= GETDATE()' : 'DATE(date_posted) <= CURRENT_DATE') . ')
ORDER BY date_posted DESC',
array((int) $assocType, (int) $assocId),
$rangeInfo
@@ -303,14 +304,15 @@ function getAnnouncementsNotExpiredByAssocId($assocType, $assocId, $rangeInfo =
* @return object DAOResultFactory containing matching Announcements
*/
function getNumAnnouncementsNotExpiredByAssocId($assocType, $assocId, $numAnnouncements, $rangeInfo = null) {
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieveRange(
'SELECT *
FROM announcements
WHERE assoc_type = ?
AND assoc_id = ?
AND (date_expire IS NULL OR DATE(date_expire) > CURRENT_DATE)
AND (DATE(date_posted) <= CURRENT_DATE)
ORDER BY date_posted DESC LIMIT ?',
AND (date_expire IS NULL OR ' . ($isSqlServer ? 'date_expire > GETDATE()' : ' DATE(date_expire) > CURRENT_DATE') . ')
AND (' . ($isSqlServer ? 'date_posted <= GETDATE()' : 'DATE(date_posted) <= CURRENT_DATE') . ')
ORDER BY date_posted DESC ' . ($isSqlServer ? 'OFFSET 0 ROWS FETCH NEXT CAST(? AS int) ROWS ONLY' : 'LIMIT ?'),
array((int) $assocType, (int) $assocId, (int) $numAnnouncements),
$rangeInfo
);
@@ -169,7 +169,8 @@ function updateConfig($file, $params) {
if (preg_match('/[^\w\-\/]/', $value)) {
// Escape strings containing non-alphanumeric characters
$valueString = '"' . $value . '"';
// Make sure no single backslash is present inside the quotes.
$valueString = '"' . str_replace('\\', '\\\\', $value) . '"';

This comment has been minimized.

@asmecher

asmecher Jun 20, 2018

Member

I think it would be better to wrap addslashes around $value -- I suspect there are other characters that need escaping, and that would take care of it symmetrically.

This comment has been minimized.

@nibou230

nibou230 Jun 20, 2018

Yeah, nice solution here.

This comment has been minimized.

@asmecher

asmecher Jun 20, 2018

Member

Committed: a6f72cd

} else {
$valueString = $value;
}
Copy path View file
@@ -421,7 +421,7 @@ function checkCSRF() {
* Get the remote IP address of the current request.
* @return string
*/
function getRemoteAddr() {
static function getRemoteAddr() {

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

I know the kinds of warnings you're working to resolve with these, but longer term, our solution will be to have a $request instance available to call on (rather than static calls). So the warnings will be helpful in identifying these.

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

On our side, we get an exception in this file when we try to upload a file somewhere in the application. Here is the exceptions I get for this event.

PHP Deprecated:  Non-static method PKPRequest::getUserVar() should not be called statically in C:\inetpub\wwwroot\ojs\lib\pkp\classes\form\Form.inc.php on line 369

PHP Deprecated:  Non-static method PKPRequest::_checkThis() should not be called statically in C:\inetpub\wwwroot\ojs\lib\pkp\classes\core\PKPRequest.inc.php on line 592

PHP Deprecated:  Methods with the same name as their class will not be constructors in a future version of PHP; Smarty_Compiler has a deprecated constructor in C:\inetpub\wwwroot\ojs\lib\pkp\lib\vendor\smarty\smarty\libs\Smarty_Compiler.class.php on line 35

Every changes we made is generally to prevent the application throwing an error and freezing at the same time at the user interface side. I know it's certainly not the right way to fix it, but we can't let the application freeze each time we try to upload a file. Can you hint us on how to resolve this problem?

This is also linked to the comment below.

This comment has been minimized.

@asmecher

asmecher Apr 18, 2018

Member

Yes, simply configuring PHP to direct warnings/errors to the log file and not the browser should resolve the UI freezing. We're working to resolve the warnings gradually but it'll take a few releases.

PKPRequest::_checkThis();
$ipaddr =& Registry::get('remoteIpAddr'); // Reference required.
@@ -560,7 +560,7 @@ function &getSession() {
* Get the user associated with the current request.
* @return User
*/
function &getUser() {
static function &getUser() {
$_this = PKPRequest::_checkThis();
$user =& Registry::get('user', true, null);
@@ -588,7 +588,7 @@ function &getUser() {
* Get the value of a GET/POST variable.
* @return mixed
*/
function getUserVar($key) {
static function getUserVar($key) {
$_this = PKPRequest::_checkThis();
// special treatment for APIRouter. APIHandler gets to fetch parameter first
@@ -815,10 +815,11 @@ function url($context = null, $page = null, $op = null, $path = null,
*
* @return PKPRequest
*/
function &_checkThis() {
if (isset($this) && is_a($this, 'PKPRequest')) {
static function &_checkThis() {
// Probably not a good fix, but this is working as expected.
/* if (isset($this) && is_a($this, 'PKPRequest')) {
return $this;
} else {
} else {*/

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

(This stop-gap is necessary while we refactor Request static calls into instance calls.)

// This call is deprecated. We don't trigger a
// deprecation error, though, as there are so
// many instances of this error that it has a
@@ -830,7 +831,7 @@ function &_checkThis() {
$instance =& Registry::get('request');
assert(!is_null($instance));
return $instance;
}
/* }*/
}
/**
@@ -444,7 +444,9 @@ static function mime_content_type($filename, $suggestedExtension = '') {
}
// Check ambiguous mimetypes against extension
$ext = array_pop(explode('.',$filename));
// The result of "explode" can't be passed directly to "array_pop" in PHP 7.

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

I'm using PHP7 and haven't run into this -- are you getting a warning?

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

We are getting this exception, which is causing the UI to freeze even if the action is completed.

PHP Notice:  Only variables should be passed by reference in C:\inetpub\wwwroot\ojs\lib\pkp\classes\core\PKPString.inc.php on line 447

This comment has been minimized.

@asmecher

asmecher Apr 18, 2018

Member

Ah, OK. Happy to have warnings like this fixed. FYI, it's not recommended to have warning/error messages displayed to the browser, and if they are, it'll probably explain why the UI is freezing when you encounter a cosmetic warning.

This comment has been minimized.

@asmecher
$exploded = explode('.',$filename);
$ext = array_pop($exploded);
if ($suggestedExtension) {
$ext = $suggestedExtension;
}
Copy path View file
@@ -160,7 +160,20 @@ function &retrieveLimit($sql, $params = false, $numRows = false, $offset = false
$start = Core::microtime();
$dataSource = $this->getDataSource();
$result = $dataSource->selectLimit($sql, $numRows === false ? -1 : $numRows, $offset === false ? -1 : $offset, $params !== false && !is_array($params) ? array($params) : $params);
if ($dataSource->errorNo()) {
// For MS SQL, this error can be misinterpreted.
$isSqlServer = Config::getVar('database', 'ms_sql');
$errorNo = $dataSource->errorNo();
if ($isSqlServer && $errorNo === 'IMSSP') {
$errorInfo = $dataSource->_stmt->errorInfo();
$errorId = $errorInfo ? $errorInfo[1] : null;
if ($errorId === -15) {
return $result;
}
}
if ($errorNo) {
fatalError('DB Error: ' . $dataSource->errorMsg());
}
return $result;
@@ -226,10 +239,26 @@ function update($sql, $params = false, $callHooks = true, $dieOnError = true) {
$start = Core::microtime();
$dataSource = $this->getDataSource();
$dataSource->execute($sql, $params !== false && !is_array($params) ? array($params) : $params);
if ($dieOnError && $dataSource->errorNo()) {
$errorNo = $dataSource->errorNo();
// For MS SQL (on an update), the statement query is trying to fetch a
// non existing row and this action is throwing the exception code -15.

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

Very weird -- is this documented by ADODB somewhere?

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

I did not see any documentation about this kind of problem for ADODB. It is probably how the library is used with the DAOs and SQL server. Somewhere, we try to fetch the non-existent results of the update.

This comment has been minimized.

@asmecher

asmecher Apr 18, 2018

Member

Can you let me know more about how this condition is triggered? Unfortunately I can't run SQL Server myself.

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

It seems to be pretty frequent. On each update and in some cases in the selectLimit function. This fix is the fastest one I found to resolve the problem without investigating too deep in the libraries code.

// This should not be interpreted as an exception at this point.
$isSqlServer = Config::getVar('database', 'ms_sql');
if ($isSqlServer && $errorNo === 'IMSSP') {
$errorInfo = $dataSource->_stmt->errorInfo();
$errorId = $errorInfo ? $errorInfo[1] : null;
if ($errorId === -15) {
return true;
}
}
if ($dieOnError && $errorNo) {
fatalError('DB Error: ' . $dataSource->errorMsg());
}
return $dataSource->errorNo() == 0 ? true : false;
return $errorNo == 0 ? true : false;
}
/**
@@ -113,7 +113,8 @@ function initCustomDBConnection($driver, $host, $username, $password, $databaseN
function initConn() {
require_once('lib/pkp/lib/adodb/adodb.inc.php');
$this->dbconn = ADONewConnection($this->driver);
$isSqlServer = Config::getVar('database', 'ms_sql');
$this->dbconn = ADONewConnection($isSqlServer ? 'pdo' : $this->driver);
if ($this->connectOnInit) {
return $this->connect();
@@ -92,9 +92,12 @@ function getUploadedFileName($fileName) {
*/
function getUploadedFileType($fileName) {
if (isset($_FILES[$fileName])) {
// The result of "explode" can't be passed directly to "array_pop" in PHP 7.
$exploded = explode('.',$_FILES[$fileName]['name']);
$type = PKPString::mime_content_type(
$_FILES[$fileName]['tmp_name'], // Location on server
array_pop(explode('.',$_FILES[$fileName]['name'])) // Extension on client machine
array_pop($exploded) // Extension on client machine

This comment has been minimized.

@asmecher
);
if (!empty($type)) return $type;
@@ -34,7 +34,8 @@ function &getTemporaryFile($fileId, $userId) {
$returner = null;
if (isset($result) && $result->RecordCount() != 0) {
$returner =& $this->_returnTemporaryFileFromRow($result->GetRowAssoc(false));
$tempReturner = $this->_returnTemporaryFileFromRow($result->GetRowAssoc(false));
$returner =& $tempReturner;
}
$result->Close();
@@ -48,7 +48,8 @@ function getFile($fileId, $userId) {
* @param $fileId int
*/
function deleteFile($fileId, $userId) {
$temporaryFile =& $this->getFile($fileId, $userId);
$file = $this->getFile($fileId, $userId);
$temporaryFile =& $file;
parent::deleteFile($this->getBasePath() . $temporaryFile->getServerFileName());
@@ -63,7 +64,8 @@ function deleteFile($fileId, $userId) {
* @return boolean
*/
function downloadFile($fileId, $userId, $inline = false) {
$temporaryFile =& $this->getFile($fileId, $userId);
$file = $this->getFile($fileId, $userId);
$temporaryFile =& $file;
if (isset($temporaryFile)) {
$filePath = $this->getBasePath() . $temporaryFile->getServerFileName();
return parent::downloadFile($filePath, null, $inline);
@@ -96,7 +98,8 @@ function handleUpload($fileName, $userId) {
$temporaryFile->setUserId($userId);
$temporaryFile->setServerFileName($newFileName);
$temporaryFile->setFileType(PKPString::mime_content_type($this->getBasePath() . $newFileName, array_pop(explode('.', $_FILES[$fileName]['name']))));
$exploded = explode('.', $_FILES[$fileName]['name']);
$temporaryFile->setFileType(PKPString::mime_content_type($this->getBasePath() . $newFileName, array_pop($exploded)));

This comment has been minimized.

@asmecher

asmecher Jun 20, 2018

Member

All changes to this file merged: f75c688

$temporaryFile->setFileSize($_FILES[$fileName]['size']);
$temporaryFile->setOriginalFileName($this->truncateFileName($_FILES[$fileName]['name'], 127));
$temporaryFile->setDateUploaded(Core::getCurrentDate());
@@ -312,7 +312,9 @@ function getObjectsByGroup($groupSymbolic, $contextId = CONTEXT_ID_NONE, $getTem
// result set that comply with the current runtime
// environment.
$matchingFilters = array();
foreach($result->GetAssoc() as $filterRow) {
$isSqlServer = Config::getVar('database', 'ms_sql');
$rows = $isSqlServer ? $result->GetRows() : $result->GetAssoc();

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

Since we're not using the key in the foreach loop, I think we can just use GetRows for all drivers.

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

So I can remove the condition and use GetRows for every databases. Can you confirm to me that this change won't cause some problems in the subsequent functions?

This comment has been minimized.

@asmecher

asmecher Apr 18, 2018

Member

Yes, in this case we're not using the returned array key at all.

foreach($rows as $filterRow) {
$filterInstance = $this->_fromRow($filterRow);
if (!$checkRuntimeEnvironment || $filterInstance->isCompatibleWithRuntimeEnvironment()) {
$matchingFilters[$filterInstance->getId()] = $filterInstance;
@@ -103,7 +103,8 @@ function &configureFilter($filterNode, $persist = true) {
$similarFilterFactory = $filterDao->getObjectsByGroupAndClass($filterGroupSymbolic, $filterClassName, 0, $isTemplate);
if ($similarFilterFactory->getCount() > 0) {
// 1) Find similar filters.
$similarFilters =& $similarFilterFactory->toArray();
$filters = $similarFilterFactory->toArray();
$similarFilters =& $filters;
// 2) Go through similar filters and eliminate them
// if they don't have the exact same settings.
@@ -146,6 +146,12 @@ static function initialize($request) {
date_default_timezone_set($timeZone);
if (Config::getVar('general', 'installed')) {
$isSqlServer = Config::getVar('database', 'ms_sql');
if ($isSqlServer) {

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

For cleanliness, I'd suggest adding a new case below to deal with this. It might cost a few extra cycles in some cases, but it's more compact/consistent.

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

All good, I am gonna add a static function to get the offset from UTC to avoid calling this portion of code each and every time with SQL server.

// Set the time zone directly on the instance running MS SQL.
return;
}
// Set the time zone for DB
// Get the offset from UTC
$now = new DateTime();
@@ -79,6 +79,7 @@ function __construct($request) {
'informix' => array('ifx', 'Informix'),
'sybase' => array('sybase', 'Sybase'),
'odbc' => array('odbc', 'ODBC'),
'pdo_sqlsrv' => array('pdo_sqlsrv', 'PDO SQL Server')

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

If this is simply a PDO connection, and doesn't need to know anything particular about SQL Server, then why not just call it "PDO" here? It's possible that other connections could even be used. (I think this will only be useful if we can get rid of SQL Server-specific SQL formulations but so far I think that's possible.)

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

(i.e. use the same driver name that ADODB expects, rather than pdo_sqlsrv)

);
// Validation checks for this form
@@ -64,14 +64,15 @@ function _getByEventType($assocType, $assocId, $eventType, $userId = null, $rang
(int) $eventType);
if ($userId) $params[] = $userId;
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieveRange(
'SELECT e.*
FROM email_log e' .
($userId ? ' LEFT JOIN email_log_users u ON e.log_id = u.email_log_id' : '') .
' WHERE e.assoc_type = ? AND
e.assoc_id = ? AND
e.event_type = ?' .
($userId ? ' AND u.user_id = ?' : ''),
e.event_type = ?'
. ($userId && !$isSqlServer ? ' AND u.user_id = ?' : ''),

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

Won't this cause the function to return matches from other users? Is there a reason SQL Server won't accept the condition?

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

You are right on this one, we should not exclude the condition. Sadly, there is a major problem we can't totally explain : the "email_log_users" table is totally empty. This said, SQL server can't return any results with the user ID filter.

The problem seems located in the method "_insertLogUserIds" when the following pattern is used :

$pattern = '/(?<=\<)[^\>]*(?=\>)/';
preg_match_all($pattern, $recipients, $matches);

Why the method is expecting emails formed this way : < "EMAIL" >? In our case, the characters < and > are not present in any conditions within the recipients.

This comment has been minimized.

@asmecher

asmecher Apr 19, 2018

Member

@nibou230, I suspect it's expecting emails to be provided in the form Screen Name <emailaddress@domain.com>.

$params,
$rangeInfo
);
@@ -25,7 +25,7 @@ class SubmissionFileLog extends SubmissionLog {
* @param $params array optional
* @return object SubmissionLogEntry iff the event was logged
*/
static function logEvent($request, &$submissionFile, $eventType, $messageKey, $params = array()) {
static function logEvent($request, $submissionFile, $eventType, $messageKey, $params = array()) {

This comment has been minimized.

@asmecher
// Create a new entry object
$submissionFileEventLogDao = DAORegistry::getDAO('SubmissionFileEventLogDAO');
$entry = $submissionFileEventLogDao->newDataObject();
@@ -705,7 +705,12 @@ function updateSchema($hookName, $args) {
$schemaXMLParser = new adoSchema($installer->dbconn);
$dict =& $schemaXMLParser->dict;
$dict->SetCharSet($installer->dbconn->charSet);
$isSqlServer = Config::getVar('database', 'ms_sql');
if (!$isSqlServer) {
$dict->SetCharSet($installer->dbconn->charSet);
}
$sql = $schemaXMLParser->parseSchema($this->getInstallSchemaFile());
if ($sql) {
$result = $installer->executeSQL($sql);
@@ -33,14 +33,15 @@ function getById($reviewFormId, $assocType = null, $assocId = null) {
$params[] = (int) $assocId;
}
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieve (
'SELECT rf.*,
SUM(CASE WHEN ra.date_completed IS NOT NULL THEN 1 ELSE 0 END) AS complete_count,
SUM(CASE WHEN ra.review_id IS NOT NULL AND ra.date_completed IS NULL THEN 1 ELSE 0 END) AS incomplete_count
FROM review_forms rf
LEFT JOIN review_assignments ra ON (ra.review_form_id = rf.review_form_id)
WHERE rf.review_form_id = ? AND rf.assoc_type = ? AND rf.assoc_id = ?
GROUP BY rf.review_form_id',
GROUP BY rf.review_form_id' . ($isSqlServer ? ', rf.seq, rf.assoc_type, rf.assoc_id, rf.is_active' : ''),
$params
);
@@ -211,14 +212,15 @@ function deleteByAssoc($assocType, $assocId) {
* @return DAOResultFactory containing matching ReviewForms
*/
function getByAssocId($assocType, $assocId, $rangeInfo = null) {
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieveRange(
'SELECT rf.*,
SUM(CASE WHEN ra.date_completed IS NOT NULL THEN 1 ELSE 0 END) AS complete_count,
SUM(CASE WHEN ra.review_id IS NOT NULL AND ra.date_completed IS NULL THEN 1 ELSE 0 END) AS incomplete_count
FROM review_forms rf
LEFT JOIN review_assignments ra ON (ra.review_form_id = rf.review_form_id)
WHERE rf.assoc_type = ? AND rf.assoc_id = ?
GROUP BY rf.review_form_id
GROUP BY rf.review_form_id' . ($isSqlServer ? ', rf.seq, rf.assoc_type, rf.assoc_id, rf.is_active' : '') . '
ORDER BY rf.seq',
array((int) $assocType, (int) $assocId), $rangeInfo
);
@@ -234,14 +236,15 @@ function getByAssocId($assocType, $assocId, $rangeInfo = null) {
* @return DAOResultFactory containing matching ReviewForms
*/
function getActiveByAssocId($assocType, $assocId, $rangeInfo = null) {
$isSqlServer = Config::getVar('database', 'ms_sql');
$result = $this->retrieveRange(
'SELECT rf.*,
SUM(CASE WHEN ra.date_completed IS NOT NULL THEN 1 ELSE 0 END) AS complete_count,
SUM(CASE WHEN ra.review_id IS NOT NULL AND ra.date_completed IS NULL THEN 1 ELSE 0 END) AS incomplete_count
FROM review_forms rf
LEFT JOIN review_assignments ra ON (ra.review_form_id = rf.review_form_id)
WHERE rf.assoc_type = ? AND rf.assoc_id = ? AND rf.is_active = 1
GROUP BY rf.review_form_id
GROUP BY rf.review_form_id' . ($isSqlServer ? ', rf.seq, rf.assoc_type, rf.assoc_id, rf.is_active' : '') . '
ORDER BY rf.seq',
array((int) $assocType, (int) $assocId), $rangeInfo
);
@@ -51,7 +51,7 @@ public function __construct() {
public function getUsers($contextId, $args = array()) {
$userListQB = $this->_buildGetUsersQueryObject($contextId, $args);
$userListQO = $userListQB->get();
$range = new DBResultRange($args['count'], null, isset($args['offset'])?$args['offset']:0);
$range = new DBResultRange(isset($args['count']) ? $args['count'] : null, null, isset($args['offset'])?$args['offset']:0);
$userDao = DAORegistry::getDAO('UserDAO');
$result = $userDao->retrieveRange($userListQO->toSql(), $userListQO->getBindings(), $range);
$queryResults = new DAOResultFactory($result, $userDao, '_returnUserFromRowWithData');
@@ -37,7 +37,9 @@ protected function bootstrap() {
// Map valid OJS3 config options to Illuminate database drivers
$driver = strtolower(Config::getVar('database', 'driver'));
if (substr($driver, 0, 8) === 'postgres') {
if (strpos($driver, 'sqlsrv') !== false) {
$driver = 'sqlsrv';

This comment has been minimized.

@asmecher

asmecher Apr 17, 2018

Member

(Watch out for space-based indents.)

This comment has been minimized.

@nibou230

nibou230 Apr 18, 2018

Yes, I will do my best to avoid those space-based indents, I saw the documentation about them after all the modifications we made to support SQL server.

} elseif (substr($driver, 0, 8) === 'postgres') {
$driver = 'pgsql';
} else {
$driver = 'mysql';
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.