Merge branch '2'

# Conflicts:
 #	.travis.yml
This commit is contained in:
Robbie Averill 2019-01-09 09:36:57 +01:00
commit 6ca8572ce5
6 changed files with 216 additions and 40 deletions

View File

@ -6,35 +6,50 @@ cache:
directories: directories:
- $HOME/.composer/cache/files - $HOME/.composer/cache/files
php:
- 7.1
- 7.2
- nightly
env: env:
global: global:
- DB=SQLITE - DB=SQLITE
- PDO=1
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- php: 5.6
env:
- CORE_VERSION=1.0.x-dev
- PDO=0
- php: 7.0
env:
- CORE_VERSION=1.1.x-dev
- PDO=1
- php: 7.1
env:
- CORE_VERSION=4.2.x-dev
- PDO=0
- php: 7.2 - php: 7.2
env: PDO=0 PHPCS_TEST=1 env:
allow_failure: - CORE_VERSION=4.3.x-dev
- php: nightly - PDO=0
- php: 7.3
env:
- CORE_VERSION=4.x-dev
- PDO=1
- PHPCS_TEST=1
before_script: before_script:
# Init PHP # Init PHP
- phpenv rehash - phpenv rehash
- phpenv config-rm xdebug.ini - phpenv config-rm xdebug.ini || true
- export PATH=~/.composer/vendor/bin:$PATH - export PATH=~/.composer/vendor/bin:$PATH
- echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
# Install composer dependencies # Install composer dependencies
- composer validate - composer validate
- composer require --no-update silverstripe/recipe-cms:2.x-dev - composer require --no-update silverstripe/recipe-cms:$CORE_VERSION
- composer install --prefer-source --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile - composer install --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile
- if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi - if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi
script: script:

View File

@ -1,6 +1,7 @@
# SQLite3 Module # SQLite3 Module
[![Build Status](https://travis-ci.org/silverstripe-labs/silverstripe-sqlite3.png?branch=master)](https://travis-ci.org/silverstripe-labs/silverstripe-sqlite3) [![Build Status](https://travis-ci.org/silverstripe/silverstripe-sqlite3.png?branch=master)](https://travis-ci.org/silverstripe/silverstripe-sqlite3)
[![SilverStripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
## Maintainer Contact ## Maintainer Contact

View File

@ -5,28 +5,28 @@ SilverStripe\Core\Injector\Injector:
SQLite3PDODatabase: SQLite3PDODatabase:
class: SilverStripe\SQLite\SQLite3Database class: SilverStripe\SQLite\SQLite3Database
properties: properties:
connector: %$PDOConnector connector: '%$PDOConnector'
schemaManager: %$SQLite3SchemaManager schemaManager: '%$SQLite3SchemaManager'
queryBuilder: %$SQLite3QueryBuilder queryBuilder: '%$SQLite3QueryBuilder'
SQLite3Database: SQLite3Database:
class: SilverStripe\SQLite\SQLite3Database class: SilverStripe\SQLite\SQLite3Database
properties: properties:
connector: %$SQLite3Connector connector: '%$SQLite3Connector'
schemaManager: %$SQLite3SchemaManager schemaManager: '%$SQLite3SchemaManager'
queryBuilder: %$SQLite3QueryBuilder queryBuilder: '%$SQLite3QueryBuilder'
# Legacy connector names # Legacy connector names
SQLiteDatabase: SQLiteDatabase:
class: SilverStripe\SQLite\SQLite3Database class: SilverStripe\SQLite\SQLite3Database
properties: properties:
connector: %$SQLite3Connector connector: '%$SQLite3Connector'
schemaManager: %$SQLite3SchemaManager schemaManager: '%$SQLite3SchemaManager'
queryBuilder: %$SQLite3QueryBuilder queryBuilder: '%$SQLite3QueryBuilder'
SQLitePDODatabase: SQLitePDODatabase:
class: SilverStripe\SQLite\SQLite3Database class: SilverStripe\SQLite\SQLite3Database
properties: properties:
connector: %$SQLite3Connector connector: '%$SQLite3Connector'
schemaManager: %$SQLite3SchemaManager schemaManager: '%$SQLite3SchemaManager'
queryBuilder: %$SQLite3QueryBuilder queryBuilder: '%$SQLite3QueryBuilder'
SQLite3Connector: SQLite3Connector:
class: SilverStripe\SQLite\SQLite3Connector class: SilverStripe\SQLite\SQLite3Connector
type: prototype type: prototype

View File

@ -60,6 +60,16 @@ class SQLite3Database extends Database
*/ */
protected $livesInMemory = false; protected $livesInMemory = false;
/**
* @var bool
*/
protected $transactionNesting = 0;
/**
* @var array
*/
protected $transactionSavepoints = [];
/** /**
* List of default pragma values * List of default pragma values
* *
@ -348,7 +358,7 @@ class SQLite3Database extends Database
"(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%'" "(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%'"
. " OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%')" . " OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%')"
. " + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%'" . " + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%'"
. " OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescriptio " . " OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescription "
. " LIKE '%$htmlEntityRelevanceKeywords%')"; . " LIKE '%$htmlEntityRelevanceKeywords%')";
$relevance[$fileClass] = "(Name LIKE '%$relevanceKeywords%' OR Title LIKE '%$relevanceKeywords%')"; $relevance[$fileClass] = "(Name LIKE '%$relevanceKeywords%' OR Title LIKE '%$relevanceKeywords%')";
} else { } else {
@ -465,26 +475,148 @@ class SQLite3Database extends Database
public function transactionStart($transaction_mode = false, $session_characteristics = false) public function transactionStart($transaction_mode = false, $session_characteristics = false)
{ {
$this->query('BEGIN'); if ($this->transactionDepth()) {
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionDepth());
} else {
$this->query('BEGIN');
$this->transactionDepthIncrease();
}
} }
public function transactionSavepoint($savepoint) public function transactionSavepoint($savepoint)
{ {
$this->query("SAVEPOINT \"$savepoint\""); $this->query("SAVEPOINT \"$savepoint\"");
$this->transactionDepthIncrease($savepoint);
}
/**
* Fetch the name of the most recent savepoint
*
* @return string
*/
protected function getTransactionSavepointName()
{
return end($this->transactionSavepoints);
} }
public function transactionRollback($savepoint = false) public function transactionRollback($savepoint = false)
{ {
// Named transaction
if ($savepoint) { if ($savepoint) {
$this->query("ROLLBACK TO $savepoint;"); $this->query("ROLLBACK TO $savepoint;");
$this->transactionDepthDecrease();
return true;
}
// Fail if transaction isn't available
if (!$this->transactionDepth()) {
return false;
}
if ($this->transactionIsNested()) {
$this->transactionRollback($this->getTransactionSavepointName());
} else { } else {
$this->query('ROLLBACK;'); $this->query('ROLLBACK;');
$this->transactionDepthDecrease();
} }
return true;
}
public function transactionDepth()
{
return $this->transactionNesting;
} }
public function transactionEnd($chain = false) public function transactionEnd($chain = false)
{ {
$this->query('COMMIT;'); // Fail if transaction isn't available
if (!$this->transactionDepth()) {
return false;
}
if ($this->transactionIsNested()) {
$savepoint = $this->getTransactionSavepointName();
$this->query('RELEASE ' . $savepoint);
$this->transactionDepthDecrease();
} else {
$this->query('COMMIT;');
$this->resetTransactionNesting();
}
if ($chain) {
$this->transactionStart();
}
return true;
}
/**
* Indicate whether or not the current transaction is nested
* Returns false if there are no transactions, or the open
* transaction is the 'outer' transaction, i.e. not nested.
*
* @return bool
*/
protected function transactionIsNested()
{
return $this->transactionNesting > 1;
}
/**
* Increase the nested transaction level by one
* savepoint tracking is optional because BEGIN
* opens a transaction, but is not a named reference
*
* @param string $savepoint
*/
protected function transactionDepthIncrease($savepoint = null)
{
++$this->transactionNesting;
if ($savepoint) {
array_push($this->transactionSavepoints, $savepoint);
}
}
/**
* Decrease the nested transaction level by one
* and reduce the savepoint tracking if we are
* nesting, as the last one is no longer valid
*/
protected function transactionDepthDecrease()
{
if ($this->transactionIsNested()) {
array_pop($this->transactionSavepoints);
}
--$this->transactionNesting;
}
/**
* In error condition, set transactionNesting to zero
*/
protected function resetTransactionNesting()
{
$this->transactionNesting = 0;
$this->transactionSavepoints = [];
}
public function query($sql, $errorLevel = E_USER_ERROR)
{
return parent::query($sql, $errorLevel);
}
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
{
return parent::preparedQuery($sql, $parameters, $errorLevel);
}
/**
* Inspect a SQL query prior to execution
* @deprecated 2.2.0:3.0.0
* @param string $sql
*/
protected function inspectQuery($sql)
{
// no-op
} }
public function clearTable($table) public function clearTable($table)

View File

@ -55,6 +55,11 @@ class SQLite3Query extends Query
*/ */
public function numRecords() public function numRecords()
{ {
// Some queries are not iterable using fetchArray like CREATE statement
if (!$this->handle->numColumns()) {
return 0;
}
$this->handle->reset(); $this->handle->reset();
$c=0; $c=0;
while ($this->handle->fetchArray()) { while ($this->handle->fetchArray()) {

View File

@ -2,10 +2,11 @@
namespace SilverStripe\SQLite; namespace SilverStripe\SQLite;
use Exception;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Dev\Debug; use SilverStripe\Dev\Debug;
use SilverStripe\ORM\Connect\DBSchemaManager; use SilverStripe\ORM\Connect\DBSchemaManager;
use Exception; use SQLite3;
/** /**
* SQLite schema manager class * SQLite schema manager class
@ -275,21 +276,22 @@ class SQLite3SchemaManager extends DBSchemaManager
} }
$queries = array( $queries = array(
"BEGIN TRANSACTION",
"CREATE TABLE \"{$tableName}_alterfield_{$fieldName}\"(" . implode(',', $newColsSpec) . ")", "CREATE TABLE \"{$tableName}_alterfield_{$fieldName}\"(" . implode(',', $newColsSpec) . ")",
"INSERT INTO \"{$tableName}_alterfield_{$fieldName}\" SELECT {$fieldNameList} FROM \"$tableName\"", "INSERT INTO \"{$tableName}_alterfield_{$fieldName}\" SELECT {$fieldNameList} FROM \"$tableName\"",
"DROP TABLE \"$tableName\"", "DROP TABLE \"$tableName\"",
"ALTER TABLE \"{$tableName}_alterfield_{$fieldName}\" RENAME TO \"$tableName\"", "ALTER TABLE \"{$tableName}_alterfield_{$fieldName}\" RENAME TO \"$tableName\"",
"COMMIT"
); );
// Remember original indexes // Remember original indexes
$indexList = $this->indexList($tableName); $indexList = $this->indexList($tableName);
// Then alter the table column // Then alter the table column
foreach ($queries as $query) { $database = $this->database;
$this->query($query.';'); $database->withTransaction(function () use ($database, $queries, $indexList) {
} foreach ($queries as $query) {
$database->query($query . ';');
}
});
// Recreate the indexes // Recreate the indexes
foreach ($indexList as $indexName => $indexSpec) { foreach ($indexList as $indexName => $indexSpec) {
@ -318,21 +320,22 @@ class SQLite3SchemaManager extends DBSchemaManager
$oldColsStr = implode(',', $oldCols); $oldColsStr = implode(',', $oldCols);
$newColsSpecStr = implode(',', $newColsSpec); $newColsSpecStr = implode(',', $newColsSpec);
$queries = array( $queries = array(
"BEGIN TRANSACTION",
"CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" ({$newColsSpecStr})", "CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" ({$newColsSpecStr})",
"INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT {$oldColsStr} FROM \"$tableName\"", "INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT {$oldColsStr} FROM \"$tableName\"",
"DROP TABLE \"$tableName\"", "DROP TABLE \"$tableName\"",
"ALTER TABLE \"{$tableName}_renamefield_{$oldName}\" RENAME TO \"$tableName\"", "ALTER TABLE \"{$tableName}_renamefield_{$oldName}\" RENAME TO \"$tableName\"",
"COMMIT"
); );
// Remember original indexes // Remember original indexes
$oldIndexList = $this->indexList($tableName); $oldIndexList = $this->indexList($tableName);
// Then alter the table column // Then alter the table column
foreach ($queries as $query) { $database = $this->database;
$this->query($query.';'); $database->withTransaction(function () use ($database, $queries) {
} foreach ($queries as $query) {
$database->query($query . ';');
}
});
// Recreate the indexes // Recreate the indexes
foreach ($oldIndexList as $indexName => $indexSpec) { foreach ($oldIndexList as $indexName => $indexSpec) {
@ -429,6 +432,15 @@ class SQLite3SchemaManager extends DBSchemaManager
return $this->buildSQLiteIndexName($table, $index); return $this->buildSQLiteIndexName($table, $index);
} }
protected function convertIndexSpec($indexSpec)
{
$supportedIndexTypes = ['index', 'unique'];
if (isset($indexSpec['type']) && !in_array($indexSpec['type'], $supportedIndexTypes)) {
$indexSpec['type'] = 'index';
}
return parent::convertIndexSpec($indexSpec);
}
public function indexList($table) public function indexList($table)
{ {
$indexList = array(); $indexList = array();
@ -540,7 +552,18 @@ class SQLite3SchemaManager extends DBSchemaManager
// Set default // Set default
if (!empty($values['default'])) { if (!empty($values['default'])) {
$default = str_replace(array('"', "'", "\\", "\0"), "", $values['default']); /*
On escaping strings:
https://www.sqlite.org/lang_expr.html
"A string constant is formed by enclosing the string in single quotes ('). A single quote within
the string can be encoded by putting two single quotes in a row - as in Pascal. C-style escapes
using the backslash character are not supported because they are not standard SQL."
Also, there is a nifty PHP function for this. However apparently one must still be cautious of
the null character ('\0' or 0x0), as per https://bugs.php.net/bug.php?id=63419
*/
$default = SQLite3::escapeString(str_replace("\0", "", $values['default']));
return "TEXT DEFAULT '$default'"; return "TEXT DEFAULT '$default'";
} else { } else {
return 'TEXT'; return 'TEXT';