Compare commits

...

76 Commits
2.0.0 ... 2

Author SHA1 Message Date
Steve Boyd df532bb741
MNT Use gha-dispatch-ci (#71) 2023-03-20 16:31:41 +13:00
Will Rossiter 1a87b18e71
fix: remove deprecated function 2023-01-24 20:04:50 +13:00
Guy Sartorelli ee7f0e4d25
Merge pull request #65 from lekoala/patch-1
prevent php 8 complaining about null values
2022-09-06 15:59:32 +12:00
Thomas Portelange 66c1c09b74
switch back to null
users are expected to use ?? '' if needed
2022-08-31 09:00:08 +02:00
Thomas Portelange d3d03d9f79
Update code/SQLite3Connector.php
Co-authored-by: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com>
2022-08-31 08:58:48 +02:00
Guy Sartorelli f949becc6a
Merge branch '2.1' into 2 2022-08-31 12:58:44 +12:00
Thomas Portelange ecaadc029e
Enforce proper type
Otherwise it may fail when passed to preg_match

Deprecated: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated
2022-08-19 15:07:23 +02:00
Thomas Portelange 05abb3f483
prevent php 8 complaining about null values
Fix Deprecated: SQLite3::escapeString(): Passing null to parameter #1 ($string) of type string is deprecated
2022-08-19 15:04:48 +02:00
Guy Sartorelli f102cad3bf
Merge pull request #64 from creative-commoners/pulls/2.1/phpcs
MNT Add codesniffer
2022-08-05 14:48:24 +12:00
Steve Boyd d142dd77e4 MNT Add codesniffer 2022-08-05 12:33:00 +12:00
Steve Boyd a919f8a317 Merge branch '2.1' into 2 2022-08-02 19:11:46 +12:00
Steve Boyd ea06c57cfb Merge branch '2.0' into 2.1 2022-08-02 19:11:02 +12:00
Guy Sartorelli d399c27722
Merge pull request #63 from creative-commoners/pulls/2.0/standardise-modules
MNT Standardise modules
2022-08-02 16:18:19 +12:00
Steve Boyd bf07381fc9 MNT Standardise modules 2022-08-01 16:23:18 +12:00
Guy Sartorelli 2ba8fe5c50
Merge pull request #61 from elliot-sawyer/compat/8.1
PHP 8.1 compatibility
2022-07-25 12:12:09 +12:00
Elliot Sawyer f9dfa9f34c PHP 8.1 compatibility 2022-07-20 19:17:01 +12:00
Sam Minnée c95b0105f5
Merge pull request #60 from chrometoasters/pulls/transaction-mode-not-supported
Override transaction mode support check method
2019-09-25 10:03:09 +12:00
Michal Kleiner 22d46a5ef8 Override transaction mode support check method as these are not supported by SQLite 2019-09-16 14:50:16 +12:00
Robbie Averill 10c85d4179 Merge branch '2.1' into 2 2019-01-09 09:35:00 +01:00
Robbie Averill b5a6416388 Merge branch '2.0' into 2.1 2019-01-09 09:34:48 +01:00
Robbie Averill 4edf7c23f8
Merge pull request #58 from mark-cameron/issue_57_upgrade_connectors_yml_file
Added quotes to values in connectors.yml
2019-01-09 09:33:59 +01:00
Mark Cameron 3c8a06f5b9 Added quotes to values in connectors.yml 2019-01-09 00:17:49 +01:00
Robbie Averill 34a3221097
Merge pull request #52 from silverstripe/features/better-travis-matrix
FIX: Better travis matrix
2018-10-19 11:26:10 +02:00
Sam Minnee 1589089f5b FIX: Better travis matrix
- Test PHP 7.2 and 7.3
 - Test against all 4.x minor releases
2018-10-19 14:21:42 +13:00
Sam Minnée e919bdffd9
Correct travis badge 2018-10-19 11:54:37 +13:00
Maxime Rainville 40b9e876ba
Merge pull request #51 from NightJar/tighten-transactions
FIX Tighten transactions
2018-10-19 10:47:46 +13:00
NightjarNZ c2569099ce correct @deprecated docblock to be Draft PSR-5 compliant 2018-10-18 22:12:06 +13:00
NightjarNZ 5eacbe7842 FIX convert index definitions to reflect actual support
It is not uncommon for an index to be defined as e.g. 'fulltext'
which SQLite3 does not support without a module to create a
virtual table (rather than an index on an existing one). The code
already in place sees that definitions be updated to 'index' on
the fly during creation and later inspection (indexList) - which
causes issue when comparing existing table definitions to what
SilverStripe expects by DataObject configuration. This discrepency
leads to tables constantly being marked to update, although
effectively nothing actually changes. We can save these CPU cycles
and a bit of head scratching by converting to a supported index type.
2018-10-16 21:57:51 +13:00
Maxime Rainville 7add192ebf
Merge pull request #50 from NightJar/escape-enum-default-death
FIX preserve enum values with correct escaping
2018-10-12 14:37:08 +13:00
NightjarNZ 0fa6b0fde7 FIX transaction depth related errors with invalid savepoint names
The logic for cancelling a savepoint was incorrect, as the behaviour
the logic was modelled on was for a different RDBMS - where a COMMIT
would always close the most recently opened transaction.

SQLite on contrast will commit the entire transaction, not just the
most recent savepoint marker until current execution point. The correct
manner to deal with a 'partial' commit is to call RELEASE <savepoint>.

This revealed an error in the savepoint logic, in that if someone had
supplied a savepoint name instead of relying on generated ones, the
rollback command did not factor for this and always assumed generated
savepoint names - again causing error. For this reason a new class
member field has been introduced to track savepoint names in a stack
fashion.
2018-10-11 22:07:23 +13:00
NightjarNZ 62ef14f711 FIX correct nesting level mismatches causing errors
Transactions that used more than one level would cause errors if
there were consecutive calls to start a transaction - because each
query executed would clear the flag indicating that a transaction
was already in progress.

The comment for the logic to reset the nesting level on a query was
indicating that DDL (data definition language) would not work within
a transaction. This is untrue, and the module itself uses a transaction
to alter table or field names. So this function has been converted to
a no-op, deprecated to be removed in version 3 of this module. It is
also no longer called upon each query.

There have been some maintenance tidyups around this area also by
abstracting the nested transaction flag manipulations into protected
functions.
2018-10-11 00:02:12 +13:00
NightjarNZ 0efd40e5c2 FIX correct handwritten logic for transactions to use new API instead
Code in the field alteration logic had a queries defiend as strings to
begin and commit transactions involve with changing table or column names.
This was causing fatal errors as BEGIN is not a valid keyword within
a trasaction (see SQLite documentation excerpt below).

A new api has been introduced to deal with transactions programmatically,
and this module was updated to support this a few months ago. This is a
tidy up of some missed portions - consuming this API which correctly uses
SAVEPOINT when a nested transaction is required automatically.

https://www.sqlite.org/lang_transaction.html
Transactions created using BEGIN...COMMIT do not nest. For nested
transactions, use the SAVEPOINT and RELEASE commands.
2018-10-09 22:22:53 +13:00
NightjarNZ 418c1178a1 FIX preserve enum values with correct escaping
Enum values are themselves enumerated in sqlite as they are not supported
as a type. This leads to values being stored in their own table, and a
regular TEXT field being used in a MySQL ENUM's stead. The default value
for this field was being escaped with custom string replacement, and
erroneously relacing the backslash (a redundant operation). This lead
to invalid Fully Qualified Class Names in SilverStripe 4, which is a
required trait for polymorphic relationships. As a result any polymorphic
relationship not set on first write would then proceed to cause an execution
error the next time the dataobject with the relationship was fetched from
the database. By using the PHP supplied escape function for SQLite3 we can
avoid this, and restore functionality.

Relevant section of SQLite documentation to justify the removal of escaping
various characters, such as the backslash:

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.

https://www.sqlite.org/lang_expr.html
2018-10-08 23:09:24 +13:00
Daniel Hensby 4167d9fd1a FIX Make sure nested transactions get reset on implicit commits 2018-07-05 15:31:05 +12:00
Damian Mooyman 6432ceea0d
Merge pull request #46 from creative-commoners/pulls/master/add-supported-module-badge
Add supported module badge to readme
2018-06-18 10:14:43 +12:00
Dylan Wagstaff d315c61ea0 Add supported module badge to readme 2018-06-15 17:49:06 +12:00
Damian Mooyman c346e64590
Merge pull request #43 from lekoala/patch-1
return 0 for non iterable results
2018-04-16 09:08:06 +12:00
Damian Mooyman a38ab53e33
Merge pull request #39 from dhensby/pulls/2.0/nested-transactions
FIX Add nested transaction support
2018-03-13 09:25:09 +13:00
Thomas Portelange 978c371820
return 0 for non iterable results
If there are no columns, it's not a iterable result set and we can return 0. This fixes issues with things like CREATE statement.
2018-03-12 14:04:43 +01:00
Daniel Hensby b36f3598bb
Merge pull request #40 from mikenz/patch-2
Missing 'n'
2018-02-13 10:50:27 +00:00
Mike Cochrane 34648b9c05
Missing 'n' 2018-02-13 14:18:48 +13:00
Daniel Hensby f176bb0a39
FIX Add nested transaction support 2018-02-09 11:24:35 +00:00
Damian Mooyman 0e6aa26f55
Merge remote-tracking branch 'origin/2.1' into 2 2017-12-07 16:18:39 +13:00
Damian Mooyman 9b00630616
Merge remote-tracking branch 'origin/2.0' into 2.1
# Conflicts:
#	code/SQLite3Database.php
#	code/SQLiteDatabaseConfigurationHelper.php
2017-12-07 16:18:19 +13:00
Damian Mooyman e8f4e55b8a
Merge remote-tracking branch 'origin/1.4' into 2.0
# Conflicts:
#	.travis.yml
#	code/SQLite3Database.php
#	composer.json
2017-12-07 16:16:11 +13:00
Damian Mooyman 2bde2640c3
Merge pull request #1 from silverstripe-security/patch/1.4/SS-2017-008
[SS-2017-008] Fix SQL injection in search engine
2017-12-07 15:59:01 +13:00
Damian Mooyman ca4a76eaab
Update 2 branch alias to 2.2 2017-11-28 10:50:04 +13:00
Damian Mooyman 77e5a5e18c
Merge branch '2.1' into 2 2017-11-28 10:49:33 +13:00
Damian Mooyman dbf10488ee
Remove master branch alias from 2.1 branch 2017-11-28 10:49:05 +13:00
Daniel Hensby 978a5a19ce
Merge pull request #37 from open-sausages/pulls/4.0/update-styles
Update config / code styles for 4.0
2017-11-23 12:50:04 +00:00
Damian Mooyman 24cf40beaf
Update config / code styles for 4.0
Related https://github.com/silverstripe/silverstripe-framework/issues/7590
2017-11-23 14:11:10 +13:00
Daniel Hensby 4aad42c084
[SS-2017-008] Fix SQL injection in search engine 2017-11-21 16:16:32 +00:00
Damian Mooyman acaaf95d22 Merge pull request #35 from dhensby/pulls/db-index-update
Update module to work with new stricter index definitions
2017-10-12 14:03:50 +13:00
Daniel Hensby c00a11cf7e
Travis setup 2017-10-10 15:10:30 +01:00
Daniel Hensby 536ada309e
Update module to work with new stricter index definitions 2017-10-10 13:09:06 +01:00
Damian Mooyman 8d25343c41 Merge pull request #32 from open-sausages/pulls/2/vendorise-me-baby
Expose as vendor module
2017-10-03 16:17:05 +13:00
Ingo Schommer eba3c2c746 Expose as vendor module 2017-10-03 03:12:27 +13:00
Daniel Hensby 4e854b3fd2 Merge pull request #30 from kinglozzer/fix-seek
FIX: SQLite3Query::seek() failed to return a record
2017-01-13 11:56:18 +00:00
Loz Calver 0a646577fe FIX: SQLite3Query::seek() failed to return a record 2017-01-12 17:08:05 +00:00
Damian Mooyman 47d47ab2a8 Bump alias of master to 2.1 2016-11-13 21:57:58 +13:00
Damian Mooyman 7304708fa1 BUG Fix installer for 4.0 (#29) 2016-10-26 14:24:31 +13:00
Loz Calver cfdf20b253 Merge pull request #28 from mikenz/patch-1
Update for latest SS4 changes
2016-10-10 09:05:52 +01:00
Mike Cochrane dd81faac7b Update for latest SS4 changes 2016-10-10 11:13:18 +13:00
Daniel Hensby 360b70aa22 Merge pull request #27 from open-sausages/pulls/4.0/fix-path-warnings
BUG Fix errors when 'path' isn't declared explicitly
2016-09-30 12:24:10 +01:00
Damian Mooyman 6d74fc05cf
BUG Fix errors when 'path' isn't declared explicitly 2016-09-30 15:45:13 +13:00
Damian Mooyman b710ef04b3 Rename SS_ prefixed classes (#26) 2016-09-09 15:46:48 +12:00
Ingo Schommer 8001e69b71 Merge pull request #25 from open-sausages/pulls/4.0/namespace-everything
Upgrade for silverstripe namespaces
2016-09-08 16:11:21 +12:00
Damian Mooyman 1cb63311d8
Upgrade for silverstripe namespaces 2016-09-08 15:41:17 +12:00
Ingo Schommer 32ae8f8d94 Merge pull request #24 from open-sausages/pulls/4.0/namespace-cms
Update for SilverStripe\CMS namespace
2016-08-12 16:39:36 +12:00
Damian Mooyman 99e6081c42
Update for SilverStripe\CMS namespace 2016-08-12 13:22:58 +12:00
Damian Mooyman 72638cf40e BUG Fix incorrect upgrade / rules 2016-07-05 16:27:01 +12:00
Ingo Schommer d09ab6bea4 Merge pull request #23 from open-sausages/pulls/4.0/namespace
API Apply SilverStripe\SQLite namespace to module
2016-07-01 15:10:06 +12:00
Damian Mooyman e36e74ab2f API Apply SilverStripe\SQLite namespace to module 2016-06-29 13:55:45 +12:00
Loz Calver 149d1708b6 Merge pull request #20 from dhensby/pulls/1.4/travis
Update travis test coverage
2016-03-23 13:50:28 +00:00
Daniel Hensby ea1d504617 Update travis test coverage 2016-03-23 11:37:24 +00:00
Damian Mooyman 65cd866f42 Restrict 1.4 to ~3.2 2015-10-16 11:16:30 +13:00
20 changed files with 742 additions and 459 deletions

11
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

16
.github/workflows/dispatch-ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Dispatch CI
on:
# At 3:00 PM UTC, only on Sunday and Monday
schedule:
- cron: '0 15 * * 0,1'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

17
.github/workflows/keepalive.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Keepalive
on:
workflow_dispatch:
# The 15th of every month at 3:50pm UTC
schedule:
- cron: '50 15 15 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

View File

@ -1,69 +0,0 @@
inherit: true
checks:
php:
verify_property_names: true
verify_argument_usable_as_reference: true
verify_access_scope_valid: true
useless_calls: true
use_statement_alias_conflict: true
variable_existence: true
unused_variables: true
unused_properties: true
unused_parameters: true
unused_methods: true
unreachable_code: true
too_many_arguments: true
sql_injection_vulnerabilities: true
simplify_boolean_return: true
side_effects_or_types: true
security_vulnerabilities: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
require_scope_for_properties: true
require_scope_for_methods: true
require_php_tag_first: true
psr2_switch_declaration: true
psr2_class_declaration: true
property_assignments: true
prefer_while_loop_over_for_loop: true
precedence_mistakes: true
precedence_in_conditions: true
phpunit_assertions: true
php5_style_constructor: true
parse_doc_comments: true
parameter_non_unique: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
optional_parameters_at_the_end: true
one_class_per_file: true
no_unnecessary_if: true
no_trailing_whitespace: true
no_property_on_interface: true
no_non_implemented_abstract_methods: true
no_error_suppression: true
no_duplicate_arguments: true
no_commented_out_code: true
newline_at_end_of_file: true
missing_arguments: true
method_calls_on_non_object: true
instanceof_class_exists: true
foreach_traversable: true
fix_line_ending: true
fix_doc_comments: true
duplication: true
deprecated_code_usage: true
deadlock_detection_in_loops: true
code_rating: true
closure_use_not_conflicting: true
catch_class_exists: true
blank_line_after_namespace_declaration: false
avoid_multiple_statements_on_same_line: true
avoid_duplicate_types: true
avoid_conflicting_incrementers: true
avoid_closing_tag: true
assignment_of_null_return: true
argument_type_checks: true
filter:
paths: [code/*, tests/*]

View File

@ -1,28 +0,0 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
sudo: false
language: php
php:
- 5.5
- 5.6
- 7.0
env:
- DB=SQLITE CORE_RELEASE=master PDO=1
matrix:
include:
- php: 5.6
env: DB=SQLITE CORE_RELEASE=master PDO=0
before_script:
- composer self-update || true
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
- composer install
script:
- vendor/bin/phpunit framework/tests

7
.upgrade.yml Normal file
View File

@ -0,0 +1,7 @@
mappings:
SQLite3Connector: SilverStripe\SQLite\SQLite3Connector
SQLite3Database: SilverStripe\SQLite\SQLite3Database
SQLite3Query: SilverStripe\SQLite\SQLite3Query
SQLite3QueryBuilder: SilverStripe\SQLite\SQLite3QueryBuilder
SQLite3SchemaManager: SilverStripe\SQLite\SQLite3SchemaManager
SQLiteDatabaseConfigurationHelper: SilverStripe\SQLite\SQLiteDatabaseConfigurationHelper

View File

@ -1,6 +1,7 @@
# SQLite3 Module
[![Build Status](https://travis-ci.org/silverstripe-labs/silverstripe-sqlite3.png?branch=master)](https://travis-ci.org/silverstripe-labs/silverstripe-sqlite3)
[![CI](https://github.com/silverstripe/silverstripe-sqlite3/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-sqlite3/actions/workflows/ci.yml)
[![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
@ -9,26 +10,23 @@ Andreas Piening (Nickname: apiening)
## Requirements
* SilverStripe 3.2 or newer
* Silverstripe 4.0 or newer
## Installation
* If using composer, run `composer require silverstripe/sqlite3 1.4.*-dev`.
* Otherwise, download, unzip and copy the sqlite3 folder to your project root so that it becomes a
sibling of `framework/`.
* Install using composer with `composer require silverstripe/sqlite3 ^2`.
## Configuration
Either use the installer to automatically install SQLite or add this to your _config.php (right after
"require_once("conf/ConfigureFromEnv.php");" if you are using _ss_environment.php)
$databaseConfig['type'] = 'SQLiteDatabase';
$databaseConfig['type'] = 'SQLite3Database';
$databaseConfig['path'] = "/path/to/my/database/file";
Make sure the webserver has sufficient privileges to write to that folder and that it is protected from
external access.
### Sample mysite/_config.php
```php
@ -42,18 +40,14 @@ $database = 'SS_mysite';
require_once("conf/ConfigureFromEnv.php");
global $databaseConfig;
$databaseConfig = array(
"type" => 'SQLiteDatabase',
"type" => 'SQLite3Database',
"server" => 'none',
"username" => 'none',
"password" => 'none',
"database" => $database,
"path" => "/path/to/my/database/file",
);
SSViewer::set_theme('blackcandy');
SiteTree::enable_nested_urls();
```
Again: make sure that the webserver has permission to read and write to the above path (/path/to/my/database/,

View File

@ -1,3 +1 @@
<?php
Deprecation::notification_version('1.4.0', 'sqlite3');

View File

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

View File

@ -1,4 +0,0 @@
SQLLite3Database:
# Extension used to distinguish between sqllite database files and other files
# Required to handle multiple databases
database_extension: '.sqlite'

View File

@ -1,14 +1,25 @@
<?php
// Script called from ConfigureFromEnv.php
global $databaseConfig;
if(strpos($databaseConfig['type'], 'SQLite') === 0) {
// Called from DatabaseAdapterRegistry::autoconfigure($config)
use SilverStripe\Core\Environment;
use SilverStripe\SQLite\SQLite3Database;
if(defined('SS_SQLITE_DATABASE_PATH')) {
$databaseConfig['path'] = SS_SQLITE_DATABASE_PATH;
}
if(defined('SS_SQLITE_DATABASE_KEY')) {
$databaseConfig['key'] = SS_SQLITE_DATABASE_KEY;
}
if (!isset($databaseConfig)) {
global $databaseConfig;
}
// Get path
$path = Environment::getEnv(SQLite3Database::ENV_PATH);
if ($path) {
$databaseConfig['path'] = $path;
} elseif (defined(SQLite3Database::ENV_PATH)) {
$databaseConfig['path'] = constant(SQLite3Database::ENV_PATH);
}
// Get key
$key = Environment::getEnv(SQLite3Database::ENV_KEY);
if ($key) {
$databaseConfig['key'] = $key;
} elseif (defined(SQLite3Database::ENV_KEY)) {
$databaseConfig['key'] = constant(SQLite3Database::ENV_KEY);
}

View File

@ -1,45 +1,54 @@
<?php
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\SQLite\SQLiteDatabaseConfigurationHelper;
$sqliteDatabaseAdapterRegistryFields = array(
'path' => array(
'title' => 'Directory path<br /><small>Absolute path to directory, writeable by the webserver user.<br />'
. 'Recommended to be outside of your webroot</small>',
'default' => dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . '.sqlitedb'
),
'database' => array(
'title' => 'Database filename (extension .sqlite)',
'default' => 'database.sqlite'
)
'path' => array(
'title' => 'Directory path<br /><small>Absolute path to directory, writeable by the webserver user.<br />'
. 'Recommended to be outside of your webroot</small>',
'default' => dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . '.sqlitedb'
),
'database' => array(
'title' => 'Database filename (extension .sqlite)',
'default' => 'database.sqlite'
)
);
// Basic SQLLite3 Database
/** @skipUpgrade */
DatabaseAdapterRegistry::register(
array(
'class' => 'SQLite3Database',
'title' => 'SQLite 3.3+ (using SQLite3)',
'helperPath' => dirname(__FILE__).'/code/SQLiteDatabaseConfigurationHelper.php',
'supported' => class_exists('SQLite3'),
'missingExtensionText' => 'The <a href="http://php.net/manual/en/book.sqlite3.php">SQLite3</a>
array(
'class' => 'SQLite3Database',
'module' => 'sqlite3',
'title' => 'SQLite 3.3+ (using SQLite3)',
'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php',
'helperClass' => SQLiteDatabaseConfigurationHelper::class,
'supported' => class_exists('SQLite3'),
'missingExtensionText' => 'The <a href="http://php.net/manual/en/book.sqlite3.php">SQLite3</a>
PHP Extension is not available. Please install or enable it of them and refresh this page.',
'fields' => array_merge($sqliteDatabaseAdapterRegistryFields, array('key' => array(
'title' => 'Encryption key<br><small>This function is experimental and requires configuration of an '
. 'encryption module</small>',
'default' => ''
)))
)
'fields' => array_merge($sqliteDatabaseAdapterRegistryFields, array('key' => array(
'title' => 'Encryption key<br><small>This function is experimental and requires configuration of an '
. 'encryption module</small>',
'default' => ''
)))
)
);
// PDO database
/** @skipUpgrade */
DatabaseAdapterRegistry::register(
array(
'class' => 'SQLite3PDODatabase',
'title' => 'SQLite 3.3+ (using PDO)',
'helperPath' => dirname(__FILE__).'/code/SQLiteDatabaseConfigurationHelper.php',
'supported' => (class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers())),
'missingExtensionText' =>
'Either the <a href="http://php.net/manual/en/book.pdo.php">PDO Extension</a> or the
array(
'class' => 'SQLite3PDODatabase',
'module' => 'sqlite3',
'title' => 'SQLite 3.3+ (using PDO)',
'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php',
'helperClass' => SQLiteDatabaseConfigurationHelper::class,
'supported' => (class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers())),
'missingExtensionText' =>
'Either the <a href="http://php.net/manual/en/book.pdo.php">PDO Extension</a> or the
<a href="http://php.net/manual/en/book.sqlite3.php">SQLite3 PDO Driver</a>
are unavailable. Please install or enable these and refresh this page.',
'fields' => $sqliteDatabaseAdapterRegistryFields
)
'fields' => $sqliteDatabaseAdapterRegistryFields
)
);

View File

@ -1,23 +1,26 @@
<?php
namespace SilverStripe\SQLite;
use SilverStripe\ORM\Connect\DBConnector;
use SQLite3;
/**
* SQLite connector class
*
* @package SQLite3
*/
class SQLite3Connector extends DBConnector
{
/**
* The name of the database.
*
*
* @var string
*/
protected $databaseName;
/**
* Connection to the DBMS.
*
*
* @var SQLite3
*/
protected $dbConn;
@ -66,7 +69,7 @@ class SQLite3Connector extends DBConnector
/**
* Prepares the list of parameters in preparation for passing to mysqli_stmt_bind_param
*
*
* @param array $parameters List of parameters
* @return array List of parameters types and values
*/
@ -108,7 +111,7 @@ class SQLite3Connector extends DBConnector
case 'array':
case 'unknown type':
default:
user_error("Cannot bind parameter \"$value\" as it is an unsupported type ($phpType)", E_USER_ERROR);
$this->databaseError("Cannot bind parameter \"$value\" as it is an unsupported type ($phpType)");
break;
}
$values[] = array(
@ -133,14 +136,14 @@ class SQLite3Connector extends DBConnector
$type = $parsedParameters[$i]['type'];
$statement->bindValue($i+1, $value, $type);
}
// Return successful result
$handle = $statement->execute();
if ($handle) {
return new SQLite3Query($this, $handle);
}
}
// Handle error
$values = $this->parameterValues($parameters);
$this->databaseError($this->getLastError(), $errorLevel, $sql, $values);
@ -154,7 +157,7 @@ class SQLite3Connector extends DBConnector
if ($handle) {
return new SQLite3Query($this, $handle);
}
// Handle error
$this->databaseError($this->getLastError(), $errorLevel, $sql);
return null;
@ -167,13 +170,13 @@ class SQLite3Connector extends DBConnector
public function escapeString($value)
{
return $this->dbConn->escapeString($value);
return $this->dbConn->escapeString($value ?? '');
}
public function selectDatabase($name)
{
if ($name !== $this->databaseName) {
user_error("SQLite3Connector can't change databases. Please create a new database connection", E_USER_ERROR);
$this->databaseError("SQLite3Connector can't change databases. Please create a new database connection");
}
return true;
}

View File

@ -1,16 +1,46 @@
<?php
namespace SilverStripe\SQLite;
use SilverStripe\Assets\File;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Connect\Database;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\PaginatedList;
use SilverStripe\ORM\Queries\SQLSelect;
/**
* SQLite database controller class
*
* @package SQLite3
*/
class SQLite3Database extends SS_Database
class SQLite3Database extends Database
{
use Configurable;
/**
* Global environment config for setting 'path'
*/
const ENV_PATH = 'SS_SQLITE_DATABASE_PATH';
/**
* Global environment config for setting 'key'
*/
const ENV_KEY = 'SS_SQLITE_DATABASE_KEY';
/**
* Extension added to every database name
*
* @config
* @var string
*/
private static $database_extension = '.sqlite';
/**
* Database schema manager object
*
*
* @var SQLite3SchemaManager
*/
protected $schemaManager = null;
@ -18,21 +48,31 @@ class SQLite3Database extends SS_Database
/*
* This holds the parameters that the original connection was created with,
* so we can switch back to it if necessary (used for unit tests)
*
*
* @var array
*/
protected $parameters;
/*
* if we're on a In-Memory db
*
*
* @var boolean
*/
protected $livesInMemory = false;
/**
* @var bool
*/
protected $transactionNesting = 0;
/**
* @var array
*/
protected $transactionSavepoints = [];
/**
* List of default pragma values
*
*
* @todo Migrate to SS config
*
* @var array
@ -46,17 +86,17 @@ class SQLite3Database extends SS_Database
/**
* Extension used to distinguish between sqllite database files and other files.
* Required to handle multiple databases.
*
*
* @return string
*/
public static function database_extension()
{
return Config::inst()->get('SQLite3Database', 'database_extension');
return static::config()->get('database_extension');
}
/**
* Check if a database name has a valid extension
*
*
* @param string $name
* @return boolean
*/
@ -89,40 +129,30 @@ class SQLite3Database extends SS_Database
unset($parameters['memory']);
$parameters['path'] = ':memory:';
}
//We will store these connection parameters for use elsewhere (ie, unit tests)
$this->parameters = $parameters;
$this->schemaManager->flushCache();
// Ensure database name is set
if (empty($parameters['database'])) {
$parameters['database'] = 'database' . self::database_extension();
$parameters['database'] = 'database';
}
$dbName = $parameters['database'];
if (!self::is_valid_database_name($dbName)) {
// If not using the correct file extension for database files then the
// results of SQLite3SchemaManager::databaseList will be unpredictable
$extension = self::database_extension();
Deprecation::notice('3.2', "SQLite3Database now expects a database file with extension \"$extension\". Behaviour may be unpredictable otherwise.");
}
// use the very lightspeed SQLite In-Memory feature for testing
if ($this->getLivesInMemory()) {
$file = ':memory:';
} else {
// Ensure path is given
if (empty($parameters['path'])) {
$parameters['path'] = ASSETS_PATH . '/.sqlitedb';
}
$path = $this->getPath();
//assumes that the path to dbname will always be provided:
$file = $parameters['path'] . '/' . $dbName;
if (!file_exists($parameters['path'])) {
SQLiteDatabaseConfigurationHelper::create_db_dir($parameters['path']);
SQLiteDatabaseConfigurationHelper::secure_db_dir($parameters['path']);
$file = $path . '/' . $parameters['database'] . self::database_extension();
if (!file_exists($path)) {
SQLiteDatabaseConfigurationHelper::create_db_dir($path);
SQLiteDatabaseConfigurationHelper::secure_db_dir($path);
}
}
// 'path' and 'database' are merged into the full file path, which
// is the format that connectors such as PDOConnector expect
$parameters['filepath'] = $file;
@ -145,7 +175,7 @@ class SQLite3Database extends SS_Database
/**
* Retrieve parameters used to connect to this SQLLite database
*
*
* @return array
*/
public function getParameters()
@ -153,11 +183,32 @@ class SQLite3Database extends SS_Database
return $this->parameters;
}
/**
* Determine if this Db is in memory
*
* @return bool
*/
public function getLivesInMemory()
{
return isset($this->parameters['path']) && $this->parameters['path'] === ':memory:';
}
/**
* Get file path. If in memory this is null
*
* @return string|null
*/
public function getPath()
{
if ($this->getLivesInMemory()) {
return null;
}
if (empty($this->parameters['path'])) {
return ASSETS_PATH . '/.sqlitedb';
}
return $this->parameters['path'];
}
public function supportsCollations()
{
return true;
@ -170,9 +221,9 @@ class SQLite3Database extends SS_Database
/**
* Execute PRAGMA commands.
*
* @param string pragma name
* @param string value to set
*
* @param string $pragma name
* @param string $value to set
*/
public function setPragma($pragma, $value)
{
@ -181,8 +232,8 @@ class SQLite3Database extends SS_Database
/**
* Gets pragma value.
*
* @param string pragma name
*
* @param string $pragma name
* @return string the pragma value
*/
public function getPragma($pragma)
@ -236,69 +287,97 @@ class SQLite3Database extends SS_Database
* - there must not be more than one MATCH operator per statement
* - the fts3 extension needs to be available
* for now we use the MySQL implementation with the MATCH()AGAINST() uglily replaced with LIKE
*
*
* @param array $classesToSearch
* @param string $keywords Keywords as a space separated string
* @return object DataObjectSet of result pages
* @param int $start
* @param int $pageLength
* @param string $sortBy
* @param string $extraFilter
* @param bool $booleanSearch
* @param string $alternativeFileFilter
* @param bool $invertedMatch
* @return PaginatedList DataObjectSet of result pages
*/
public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false
public function searchEngine(
$classesToSearch,
$keywords,
$start,
$pageLength,
$sortBy = "Relevance DESC",
$extraFilter = "",
$booleanSearch = false,
$alternativeFileFilter = "",
$invertedMatch = false
) {
$start = (int)$start;
$pageLength = (int)$pageLength;
$keywords = $this->escapeString(str_replace(array('*', '+', '-', '"', '\''), '', $keywords));
$htmlEntityKeywords = htmlentities(utf8_decode($keywords));
$extraFilters = array('SiteTree' => '', 'File' => '');
$pageClass = 'SilverStripe\\CMS\\Model\\SiteTree';
$fileClass = 'SilverStripe\\Assets\\File';
$extraFilters = array($pageClass => '', $fileClass => '');
if ($extraFilter) {
$extraFilters['SiteTree'] = " AND $extraFilter";
$extraFilters[$pageClass] = " AND $extraFilter";
if ($alternativeFileFilter) {
$extraFilters['File'] = " AND $alternativeFileFilter";
$extraFilters[$fileClass] = " AND $alternativeFileFilter";
} else {
$extraFilters['File'] = $extraFilters['SiteTree'];
$extraFilters[$fileClass] = $extraFilters[$pageClass];
}
}
// Always ensure that only pages with ShowInSearch = 1 can be searched
$extraFilters['SiteTree'] .= ' AND ShowInSearch <> 0';
// File.ShowInSearch was added later, keep the database driver backwards compatible
$extraFilters[$pageClass] .= ' AND ShowInSearch <> 0';
// File.ShowInSearch was added later, keep the database driver backwards compatible
// by checking for its existence first
$fields = $this->getSchemaManager()->fieldList('File');
if (array_key_exists('ShowInSearch', $fields)) {
$extraFilters['File'] .= " AND ShowInSearch <> 0";
if (File::singleton()->getSchema()->fieldSpec(File::class, 'ShowInSearch')) {
$extraFilters[$fileClass] .= " AND ShowInSearch <> 0";
}
$limit = $start . ", " . (int) $pageLength;
$limit = $start . ", " . $pageLength;
$notMatch = $invertedMatch ? "NOT " : "";
if ($keywords) {
$match['SiteTree'] = "
(Title LIKE '%$keywords%' OR MenuTitle LIKE '%$keywords%' OR Content LIKE '%$keywords%' OR MetaDescription LIKE '%$keywords%' OR
Title LIKE '%$htmlEntityKeywords%' OR MenuTitle LIKE '%$htmlEntityKeywords%' OR Content LIKE '%$htmlEntityKeywords%' OR MetaDescription LIKE '%$htmlEntityKeywords%')
";
$match['File'] = "(Name LIKE '%$keywords%' OR Title LIKE '%$keywords%') AND ClassName = 'File'";
$match[$pageClass] =
"(Title LIKE '%$keywords%' OR MenuTitle LIKE '%$keywords%' OR Content LIKE '%$keywords%'"
. " OR MetaDescription LIKE '%$keywords%' OR Title LIKE '%$htmlEntityKeywords%'"
. " OR MenuTitle LIKE '%$htmlEntityKeywords%' OR Content LIKE '%$htmlEntityKeywords%'"
. " OR MetaDescription LIKE '%$htmlEntityKeywords%')";
$fileClassSQL = Convert::raw2sql($fileClass);
$match[$fileClass] =
"(Name LIKE '%$keywords%' OR Title LIKE '%$keywords%') AND ClassName = '$fileClassSQL'";
// We make the relevance search by converting a boolean mode search into a normal one
$relevanceKeywords = $keywords;
$htmlEntityRelevanceKeywords = $htmlEntityKeywords;
$relevance['SiteTree'] = "(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%' OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%') + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%' OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescription LIKE '%$htmlEntityRelevanceKeywords%')";
$relevance['File'] = "(Name LIKE '%$relevanceKeywords%' OR Title LIKE '%$relevanceKeywords%')";
$relevance[$pageClass] =
"(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%'"
. " OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%')"
. " + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%'"
. " OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescription "
. " LIKE '%$htmlEntityRelevanceKeywords%')";
$relevance[$fileClass] = "(Name LIKE '%$relevanceKeywords%' OR Title LIKE '%$relevanceKeywords%')";
} else {
$relevance['SiteTree'] = $relevance['File'] = 1;
$match['SiteTree'] = $match['File'] = "1 = 1";
$relevance[$pageClass] = $relevance[$fileClass] = 1;
$match[$pageClass] = $match[$fileClass] = "1 = 1";
}
// Generate initial queries and base table names
$baseClasses = array('SiteTree' => '', 'File' => '');
// Generate initial queries
$queries = array();
foreach ($classesToSearch as $class) {
$queries[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], "")->dataQuery()->query();
$fromArr = $queries[$class]->getFrom();
$baseClasses[$class] = reset($fromArr);
$queries[$class] = DataList::create($class)
->where($notMatch . $match[$class] . $extraFilters[$class])
->dataQuery()
->query();
}
// Make column selection lists
$select = array(
'SiteTree' => array(
$pageClass => array(
"\"ClassName\"",
"\"ID\"",
"\"ParentID\"",
@ -309,9 +388,9 @@ class SQLite3Database extends SS_Database
"\"Created\"",
"NULL AS \"Name\"",
"\"CanViewType\"",
"$relevance[SiteTree] AS Relevance"
$relevance[$pageClass] . " AS Relevance"
),
'File' => array(
$fileClass => array(
"\"ClassName\"",
"\"ID\"",
"NULL AS \"ParentID\"",
@ -322,18 +401,17 @@ class SQLite3Database extends SS_Database
"\"Created\"",
"\"Name\"",
"NULL AS \"CanViewType\"",
"$relevance[File] AS Relevance"
$relevance[$fileClass] . " AS Relevance"
)
);
// Process queries
foreach ($classesToSearch as $class) {
// There's no need to do all that joining
$queries[$class]->setFrom($baseClasses[$class]);
$queries[$class]->setFrom('"'.DataObject::getSchema()->baseDataTable($class).'"');
$queries[$class]->setSelect(array());
foreach ($select[$class] as $clause) {
if (preg_match('/^(.*) +AS +"?([^"]*)"?/i', $clause, $matches)) {
if (preg_match('/^(.*) +AS +"?([^"]*)"?/i', $clause ?? '', $matches)) {
$queries[$class]->selectField($matches[1], $matches[2]);
} else {
$queries[$class]->selectField(str_replace('"', '', $clause));
@ -348,6 +426,7 @@ class SQLite3Database extends SS_Database
$queryParameters = array();
$totalCount = 0;
foreach ($queries as $query) {
/** @var SQLSelect $query */
$querySQLs[] = $query->sql($parameters);
$queryParameters = array_merge($queryParameters, $parameters);
$totalCount += $query->unlimitedRowCount();
@ -368,7 +447,7 @@ class SQLite3Database extends SS_Database
}
$list = new PaginatedList($doSet);
$list->setPageStart($start);
$list->setPageLEngth($pageLength);
$list->setPageLength($pageLength);
$list->setTotalItems($totalCount);
return $list;
}
@ -381,6 +460,19 @@ class SQLite3Database extends SS_Database
return version_compare($this->getVersion(), '3.6', '>=');
}
/**
* Does this database support transaction modes?
*
* SQLite doesn't support transaction modes.
*
* @param string $mode
* @return bool
*/
public function supportsTransactionMode(string $mode): bool
{
return false;
}
public function supportsExtensions($extensions = array('partitions', 'tablespaces', 'clustering'))
{
if (isset($extensions['partitions'])) {
@ -396,26 +488,148 @@ class SQLite3Database extends SS_Database
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)
{
$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)
{
// Named transaction
if ($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 {
$this->query('ROLLBACK;');
$this->transactionDepthDecrease();
}
return true;
}
public function transactionDepth()
{
return $this->transactionNesting;
}
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)
@ -423,7 +637,12 @@ class SQLite3Database extends SS_Database
$this->query("DELETE FROM \"$table\"");
}
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
public function comparisonClause(
$field,
$value,
$exact = false,
$negate = false,
$caseSensitive = null,
$parameterised = false
) {
if ($exact && !$caseSensitive) {
@ -453,7 +672,7 @@ class SQLite3Database extends SS_Database
public function formattedDatetimeClause($date, $format)
{
preg_match_all('/%(.)/', $format, $matches);
preg_match_all('/%(.)/', $format ?? '', $matches);
foreach ($matches[1] as $match) {
if (array_search($match, array('Y', 'm', 'd', 'H', 'i', 's', 'U')) === false) {
user_error('formattedDatetimeClause(): unsupported format character %' . $match, E_USER_WARNING);
@ -475,9 +694,9 @@ class SQLite3Database extends SS_Database
$modifiers[] = 'localtime';
}
if (preg_match('/^now$/i', $date)) {
if (preg_match('/^now$/i', $date ?? '')) {
$date = "'now'";
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) {
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date ?? '')) {
$date = "'$date'";
}
@ -492,9 +711,9 @@ class SQLite3Database extends SS_Database
$modifiers[] = 'localtime';
}
if (preg_match('/^now$/i', $date)) {
if (preg_match('/^now$/i', $date ?? '')) {
$date = "'now'";
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) {
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date ?? '')) {
$date = "'$date'";
}
@ -514,15 +733,15 @@ class SQLite3Database extends SS_Database
$modifiers2[] = 'localtime';
}
if (preg_match('/^now$/i', $date1)) {
if (preg_match('/^now$/i', $date1 ?? '')) {
$date1 = "'now'";
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date1)) {
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date1 ?? '')) {
$date1 = "'$date1'";
}
if (preg_match('/^now$/i', $date2)) {
if (preg_match('/^now$/i', $date2 ?? '')) {
$date2 = "'now'";
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date2)) {
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date2 ?? '')) {
$date2 = "'$date2'";
}

View File

@ -1,23 +1,26 @@
<?php
namespace SilverStripe\SQLite;
use SilverStripe\ORM\Connect\Query;
use SQLite3Result;
/**
* A result-set from a SQLite3 database.
*
* @package SQLite3
*/
class SQLite3Query extends SS_Query
class SQLite3Query extends Query
{
/**
* The SQLite3Connector object that created this result set.
*
*
* @var SQLite3Connector
*/
protected $database;
/**
* The internal sqlite3 handle that points to the result set.
*
*
* @var SQLite3Result
*/
protected $handle;
@ -44,10 +47,10 @@ class SQLite3Query extends SS_Query
{
$this->handle->reset();
$i=0;
while ($i < $row && $row = @$this->handle->fetchArray()) {
while ($i <= $row && $result = @$this->handle->fetchArray(SQLITE3_ASSOC)) {
$i++;
}
return true;
return $result;
}
/**
@ -55,6 +58,12 @@ class SQLite3Query extends SS_Query
*/
public function numRecords()
{
// Some queries are not iterable using fetchArray like CREATE statement
if (!$this->handle->numColumns()) {
return 0;
}
$this->handle->reset();
$c=0;
while ($this->handle->fetchArray()) {
$c++;

View File

@ -1,13 +1,19 @@
<?php
namespace SilverStripe\SQLite;
use SilverStripe\ORM\Queries\SQLAssignmentRow;
use SilverStripe\ORM\Queries\SQLInsert;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\Connect\DBQueryBuilder;
use InvalidArgumentException;
/**
* Builds a SQL query string from a SQLExpression object
*
* @package SQLite3
*/
class SQLite3QueryBuilder extends DBQueryBuilder
{
/**
* @param SQLInsert $query
* @param array $parameters
@ -20,14 +26,15 @@ class SQLite3QueryBuilder extends DBQueryBuilder
$nl = $this->getSeparator();
$into = $query->getInto();
// Column identifiers
$columns = $query->getColumns();
// Build all rows
$rowParts = array();
foreach ($query->getRows() as $row) {
// Build all columns in this row
/** @var SQLAssignmentRow $row */
$assignments = $row->getAssignments();
// Join SET components together, considering parameters
$parts = array();
@ -50,7 +57,7 @@ class SQLite3QueryBuilder extends DBQueryBuilder
}
$columnSQL = implode(', ', $columns);
$sql = "INSERT INTO {$into}{$nl}($columnSQL){$nl}SELECT " . implode("{$nl}UNION ALL SELECT ", $rowParts);
return $sql;
}
@ -89,7 +96,7 @@ class SQLite3QueryBuilder extends DBQueryBuilder
} else {
$clause .= "LIMIT -1 ";
}
if (isset($limit['start']) && is_numeric($limit['start']) && $limit['start'] !== 0) {
$clause .= "OFFSET {$limit['start']}";
}

View File

@ -1,16 +1,22 @@
<?php
namespace SilverStripe\SQLite;
use Exception;
use SilverStripe\Control\Director;
use SilverStripe\Dev\Debug;
use SilverStripe\ORM\Connect\DBSchemaManager;
use SQLite3;
/**
* SQLite schema manager class
*
* @package SQLite3
*/
class SQLite3SchemaManager extends DBSchemaManager
{
/**
* Instance of the database controller this schema belongs to
*
*
* @var SQLite3Database
*/
protected $database = null;
@ -28,7 +34,7 @@ class SQLite3SchemaManager extends DBSchemaManager
* @var boolean
*/
public static $vacuum = true;
public function createDatabase($name)
{
// Ensure that any existing database is cleared before connection
@ -41,44 +47,48 @@ class SQLite3SchemaManager extends DBSchemaManager
if ($this->database->getLivesInMemory()) {
return;
}
// If using file based database ensure any existing file is removed
$parameters = $this->database->getParameters();
$fullpath = $parameters['path'] . '/' . $name;
$path = $this->database->getPath();
$fullpath = $path . '/' . $name . SQLite3Database::database_extension();
if (is_writable($fullpath)) {
unlink($fullpath);
}
}
public function databaseList()
{
$parameters = $this->database->getParameters();
// If in-memory use the current database name only
if ($this->database->getLivesInMemory()) {
return array($parameters['database']);
return array(
$this->database->getConnector()->getSelectedDatabase()
?: 'database'
);
}
// If using file based database enumerate files in the database directory
$directory = $parameters['path'];
$directory = $this->database->getPath();
$files = scandir($directory);
// Filter each file in this directory
$databases = array();
if ($files !== false) {
foreach ($files as $file) {
// Filter non-files
if (!is_file("$directory/$file")) {
continue;
}
// Filter those with correct extension
if (!SQLite3Database::is_valid_database_name($file)) {
continue;
}
$databases[] = $file;
// Filter non-files
if (!is_file("$directory/$file")) {
continue;
}
// Filter those with correct extension
if (!SQLite3Database::is_valid_database_name($file)) {
continue;
}
if ($extension = SQLite3Database::database_extension()) {
$databases[] = substr($file, 0, -strlen($extension));
} else {
$databases[] = $file;
}
}
}
return $databases;
@ -89,7 +99,7 @@ class SQLite3SchemaManager extends DBSchemaManager
$databases = $this->databaseList();
return in_array($name, $databases);
}
/**
* Empties any cached enum values
*/
@ -97,14 +107,14 @@ class SQLite3SchemaManager extends DBSchemaManager
{
$this->enum_map = array();
}
public function schemaUpdate($callback)
{
// Set locking mode
$this->database->setPragma('locking_mode', 'EXCLUSIVE');
$this->checkAndRepairTable();
$this->flushCache();
// Initiate schema update
$error = null;
try {
@ -112,10 +122,10 @@ class SQLite3SchemaManager extends DBSchemaManager
} catch (Exception $ex) {
$error = $ex;
}
// Revert locking mode
$this->database->setPragma('locking_mode', SQLite3Database::$default_pragma['locking_mode']);
if ($error) {
throw $error;
}
@ -123,13 +133,13 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Empty a specific table
*
*
* @param string $table
*/
public function clearTable($table)
{
if ($table != 'SQLiteEnums') {
$this->dbConn->query("DELETE FROM \"$table\"");
$this->query("DELETE FROM \"$table\"");
}
}
@ -162,8 +172,14 @@ class SQLite3SchemaManager extends DBSchemaManager
return $table;
}
public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null,
$alteredIndexes = null, $alteredOptions = null, $advancedOptions = null
public function alterTable(
$tableName,
$newFields = null,
$newIndexes = null,
$alteredFields = null,
$alteredIndexes = null,
$alteredOptions = null,
$advancedOptions = null
) {
if ($newFields) {
foreach ($newFields as $fieldName => $fieldSpec) {
@ -189,7 +205,7 @@ class SQLite3SchemaManager extends DBSchemaManager
}
}
}
public function renameTable($oldTableName, $newTableName)
{
$this->query("ALTER TABLE \"$oldTableName\" RENAME TO \"$newTableName\"");
@ -199,9 +215,9 @@ class SQLite3SchemaManager extends DBSchemaManager
{
$ok = true;
if (!SapphireTest::using_temp_db() && !self::$checked_and_repaired) {
if (!self::$checked_and_repaired) {
$this->alterationMessage("Checking database integrity", "repaired");
// Check for any tables with failed integrity
if ($messages = $this->query('PRAGMA integrity_check')) {
foreach ($messages as $message) {
@ -211,12 +227,12 @@ class SQLite3SchemaManager extends DBSchemaManager
}
}
}
// If enabled vacuum (clean and rebuild) the database
if (self::$vacuum) {
$this->query('VACUUM', E_USER_NOTICE);
$message = $this->database->getConnector()->getLastError();
if (preg_match('/authoriz/', $message)) {
if (preg_match('/authoriz/', $message ?? '')) {
$this->alterationMessage("VACUUM | $message", "error");
} else {
$this->alterationMessage("VACUUMing", "repaired");
@ -224,7 +240,7 @@ class SQLite3SchemaManager extends DBSchemaManager
}
self::$checked_and_repaired = true;
}
return $ok;
}
@ -260,21 +276,22 @@ class SQLite3SchemaManager extends DBSchemaManager
}
$queries = array(
"BEGIN TRANSACTION",
"CREATE TABLE \"{$tableName}_alterfield_{$fieldName}\"(" . implode(',', $newColsSpec) . ")",
"INSERT INTO \"{$tableName}_alterfield_{$fieldName}\" SELECT {$fieldNameList} FROM \"$tableName\"",
"DROP TABLE \"$tableName\"",
"ALTER TABLE \"{$tableName}_alterfield_{$fieldName}\" RENAME TO \"$tableName\"",
"COMMIT"
);
// Remember original indexes
$indexList = $this->indexList($tableName);
// Then alter the table column
foreach ($queries as $query) {
$this->query($query.';');
}
$database = $this->database;
$database->withTransaction(function () use ($database, $queries, $indexList) {
foreach ($queries as $query) {
$database->query($query . ';');
}
});
// Recreate the indexes
foreach ($indexList as $indexName => $indexSpec) {
@ -290,7 +307,7 @@ class SQLite3SchemaManager extends DBSchemaManager
if (!array_key_exists($oldName, $oldFieldList)) {
return;
}
// Determine column mappings
$oldCols = array();
$newColsSpec = array();
@ -300,42 +317,62 @@ class SQLite3SchemaManager extends DBSchemaManager
}
// SQLite doesn't support direct renames through ALTER TABLE
$oldColsStr = implode(',', $oldCols);
$newColsSpecStr = implode(',', $newColsSpec);
$queries = array(
"BEGIN TRANSACTION",
"CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" (" . implode(',', $newColsSpec) . ")",
"INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT " . implode(',', $oldCols) . " FROM \"$tableName\"",
"CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" ({$newColsSpecStr})",
"INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT {$oldColsStr} FROM \"$tableName\"",
"DROP TABLE \"$tableName\"",
"ALTER TABLE \"{$tableName}_renamefield_{$oldName}\" RENAME TO \"$tableName\"",
"COMMIT"
);
// Remember original indexes
$oldIndexList = $this->indexList($tableName);
// Then alter the table column
foreach ($queries as $query) {
$this->query($query.';');
}
$database = $this->database;
$database->withTransaction(function () use ($database, $queries) {
foreach ($queries as $query) {
$database->query($query . ';');
}
});
// Recreate the indexes
foreach ($oldIndexList as $indexName => $indexSpec) {
// Rename columns to new columns
$indexSpec['value'] = preg_replace("/\"$oldName\"/i", "\"$newName\"", $indexSpec['value']);
$this->createIndex($tableName, $indexName, $indexSpec);
// Map index columns
$columns = array_filter(array_map(function ($column) use ($newName, $oldName) {
// Unchanged
if ($column !== $oldName) {
return $column;
}
// Skip obsolete fields
if (stripos($newName, '_obsolete_') === 0) {
return null;
}
return $newName;
}, $indexSpec['columns']));
// Create index if column count unchanged
if (count($columns) === count($indexSpec['columns'])) {
$indexSpec['columns'] = $columns;
$this->createIndex($tableName, $indexName, $indexSpec);
}
}
}
public function fieldList($table)
{
$sqlCreate = $this->preparedQuery(
'SELECT sql FROM sqlite_master WHERE type = ? AND name = ?',
'SELECT "sql" FROM "sqlite_master" WHERE "type" = ? AND "name" = ?',
array('table', $table)
)->record();
$fieldList = array();
if ($sqlCreate && $sqlCreate['sql']) {
preg_match('/^[\s]*CREATE[\s]+TABLE[\s]+[\'"]?[a-zA-Z0-9_\\\]+[\'"]?[\s]*\((.+)\)[\s]*$/ims',
$sqlCreate['sql'], $matches
preg_match(
'/^[\s]*CREATE[\s]+TABLE[\s]+[\'"]?[a-zA-Z0-9_\\\]+[\'"]?[\s]*\((.+)\)[\s]*$/ims',
$sqlCreate['sql'] ?? '',
$matches
);
$fields = isset($matches[1])
? preg_split('/,(?=(?:[^\'"]*$)|(?:[^\'"]*[\'"][^\'"]*[\'"][^\'"]*)*$)/x', $matches[1])
@ -352,17 +389,16 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Create an index on a table.
*
*
* @param string $tableName The name of the table.
* @param string $indexName The name of the index.
* @param array $indexSpec The specification of the index, see Database::requireIndex() for more details.
*/
public function createIndex($tableName, $indexName, $indexSpec)
{
$parsedSpec = $this->parseIndexSpec($indexName, $indexSpec);
$sqliteName = $this->buildSQLiteIndexName($tableName, $indexName);
$columns = $parsedSpec['value'];
$unique = ($parsedSpec['type'] == 'unique') ? 'UNIQUE' : '';
$columns = $this->implodeColumnList($indexSpec['columns']);
$unique = ($indexSpec['type'] == 'unique') ? 'UNIQUE' : '';
$this->query("CREATE $unique INDEX IF NOT EXISTS \"$sqliteName\" ON \"$tableName\" ($columns)");
}
@ -371,17 +407,17 @@ class SQLite3SchemaManager extends DBSchemaManager
// Drop existing index
$sqliteName = $this->buildSQLiteIndexName($tableName, $indexName);
$this->query("DROP INDEX IF EXISTS \"$sqliteName\"");
// Create the index
$this->createIndex($tableName, $indexName, $indexSpec);
}
/**
* Builds the internal SQLLite index name given the silverstripe table and index name.
*
*
* The name is built using the table and index name in order to prevent name collisions
* between indexes of the same name across multiple tables
*
*
* @param string $tableName
* @param string $indexName
* @return string The SQLite3 name of the index
@ -390,47 +426,43 @@ class SQLite3SchemaManager extends DBSchemaManager
{
return "{$tableName}_{$indexName}";
}
protected function parseIndexSpec($name, $spec)
{
$spec = parent::parseIndexSpec($name, $spec);
// Only allow index / unique index types
if (!in_array($spec['type'], array('index', 'unique'))) {
$spec['type'] = 'index';
}
return $spec;
}
public function indexKey($table, $index, $spec)
{
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)
{
$indexList = array();
// Enumerate each index and related fields
foreach ($this->query("PRAGMA index_list(\"$table\")") as $index) {
// The SQLite internal index name, not the actual Silverstripe name
$indexName = $index["name"];
$indexType = $index['unique'] ? 'unique' : 'index';
// Determine a clean list of column names within this index
$list = array();
foreach ($this->query("PRAGMA index_info(\"$indexName\")") as $details) {
$list[] = preg_replace('/^"?(.*)"?$/', '$1', $details['name']);
}
// Safely encode this spec
$indexList[$indexName] = $this->parseIndexSpec($indexName, array(
$indexList[$indexName] = array(
'name' => $indexName,
'value' => $this->implodeColumnList($list),
'type' => $indexType
));
'columns' => $list,
'type' => $indexType,
);
}
return $indexList;
@ -446,11 +478,11 @@ class SQLite3SchemaManager extends DBSchemaManager
}
return $tables;
}
/**
* Return a boolean type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function boolean($values)
@ -461,8 +493,8 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Return a date type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function date($values)
@ -472,11 +504,11 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Return a decimal type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function decimal($values, $asDbValue = false)
public function decimal($values)
{
$default = isset($values['default']) && is_numeric($values['default']) ? $values['default'] : 0;
return "NUMERIC NOT NULL DEFAULT $default";
@ -488,25 +520,27 @@ class SQLite3SchemaManager extends DBSchemaManager
* @var array
*/
protected $enum_map = array();
/**
* Return a enum type-formatted string
*
* enums are not supported. as a workaround to store allowed values we creates an additional table
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function enum($values)
{
$tablefield = $values['table'] . '.' . $values['name'];
$enumValues = implode(',', $values['enums']);
// Ensure the cache table exists
if (empty($this->enum_map)) {
$this->query("CREATE TABLE IF NOT EXISTS \"SQLiteEnums\" (\"TableColumn\" TEXT PRIMARY KEY, \"EnumList\" TEXT)");
$this->query(
"CREATE TABLE IF NOT EXISTS \"SQLiteEnums\" (\"TableColumn\" TEXT PRIMARY KEY, \"EnumList\" TEXT)"
);
}
// Ensure the table row exists
if (empty($this->enum_map[$tablefield]) || $this->enum_map[$tablefield] != $enumValues) {
$this->preparedQuery(
@ -515,23 +549,34 @@ class SQLite3SchemaManager extends DBSchemaManager
);
$this->enum_map[$tablefield] = $enumValues;
}
// Set 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'";
} else {
return 'TEXT';
}
}
/**
* Return a set type-formatted string
* This type doesn't exist in SQLite either
*
*
* @see SQLite3SchemaManager::enum()
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function set($values)
@ -541,33 +586,33 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Return a float type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function float($values, $asDbValue = false)
public function float($values)
{
return "REAL";
}
/**
* Return a Double type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function double($values, $asDbValue = false)
public function double($values)
{
return "REAL";
}
/**
* Return a int type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function int($values, $asDbValue = false)
public function int($values)
{
return "INTEGER({$values['precision']}) " . strtoupper($values['null']) . " DEFAULT " . (int)$values['default'];
}
@ -575,41 +620,41 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Return a bigint type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function bigint($values, $asDbValue = false)
public function bigint($values)
{
return $this->int($values, $asDbValue);
return $this->int($values);
}
/**
* Return a datetime type-formatted string
* For SQLite3, we simply return the word 'TEXT', no other parameters are necessary
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function ss_datetime($values, $asDbValue = false)
public function datetime($values)
{
return "DATETIME";
}
/**
* Return a text type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function text($values, $asDbValue = false)
public function text($values)
{
return 'TEXT';
}
/**
* Return a time type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function time($values)
@ -619,11 +664,11 @@ class SQLite3SchemaManager extends DBSchemaManager
/**
* Return a varchar type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
*
* @param array $values Contains a tokenised list of info about this data type
* @return string
*/
public function varchar($values, $asDbValue = false)
public function varchar($values)
{
return "VARCHAR({$values['precision']}) COLLATE NOCASE";
}
@ -645,25 +690,27 @@ class SQLite3SchemaManager extends DBSchemaManager
public function hasTable($tableName)
{
return (bool)$this->preparedQuery(
'SELECT name FROM sqlite_master WHERE type = ? AND name = ?',
'SELECT "name" FROM "sqlite_master" WHERE "type" = ? AND "name" = ?',
array('table', $tableName)
)->first();
}
/**
* Return enum values for the given field
*
*
* @param string $tableName
* @param string $fieldName
* @return array
*/
public function enumValuesForField($tableName, $fieldName)
{
$tablefield = "$tableName.$fieldName";
// Check already cached values for this field
if (!empty($this->enum_map[$tablefield])) {
return explode(',', $this->enum_map[$tablefield]);
}
// Retrieve and cache these details from the database
$classnameinfo = $this->preparedQuery(
"SELECT EnumList FROM SQLiteEnums WHERE TableColumn = ?",
@ -674,17 +721,17 @@ class SQLite3SchemaManager extends DBSchemaManager
$this->enum_map[$tablefield] = $valueList;
return explode(',', $valueList);
}
// Fallback to empty list
return array();
}
public function dbDataType($type)
{
$values = array(
'unsigned integer' => 'INT'
);
if (isset($values[$type])) {
return $values[$type];
} else {

View File

@ -1,19 +1,26 @@
<?php
namespace SilverStripe\SQLite;
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\Dev\Install\DatabaseConfigurationHelper;
use SQLite3;
use PDO;
use Exception;
/**
* This is a helper class for the SS installer.
*
*
* It does all the specific checking for SQLiteDatabase
* to ensure that the configuration is setup correctly.
*
* @package SQLite3
*/
class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{
/**
* Create a connection of the appropriate type
*
*
* @skipUpgrade
* @param array $databaseConfig
* @param string $error Error message passed by value
* @return mixed|null Either the connection object, or null if error
@ -28,13 +35,17 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
}
$file = $databaseConfig['path'] . '/' . $databaseConfig['database'];
$conn = null;
switch ($databaseConfig['type']) {
case 'SQLite3Database':
if (empty($databaseConfig['key'])) {
$conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
} else {
$conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $databaseConfig['key']);
$conn = @new SQLite3(
$file,
SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE,
$databaseConfig['key']
);
}
break;
case 'SQLite3PDODatabase':
@ -42,10 +53,10 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
$conn = @new PDO("sqlite:$file");
break;
default:
$error = 'Invalid connection type';
$error = 'Invalid connection type: ' . $databaseConfig['type'];
return null;
}
if ($conn) {
return $conn;
} else {
@ -57,7 +68,7 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
return null;
}
}
public function requireDatabaseFunctions($databaseConfig)
{
$data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
@ -88,9 +99,9 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
/**
* Ensure a database connection is possible using credentials provided.
*
*
* @todo Validate path
*
*
* @param array $databaseConfig Associative array of db configuration, e.g. "type", "path" etc
* @return array Result - e.g. array('success' => true, 'error' => 'details of error')
*/
@ -99,37 +110,37 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
// Do additional validation around file paths
if (empty($databaseConfig['path'])) {
return array(
'success' => false,
'error' => "Missing directory path"
);
'success' => false,
'error' => "Missing directory path"
);
}
if (empty($databaseConfig['database'])) {
return array(
'success' => false,
'error' => "Missing database filename"
);
'success' => false,
'error' => "Missing database filename"
);
}
// Create and secure db directory
$path = $databaseConfig['path'];
$dirCreated = self::create_db_dir($path);
if (!$dirCreated) {
return array(
'success' => false,
'error' => sprintf('Cannot create path: "%s"', $path)
);
'success' => false,
'error' => sprintf('Cannot create path: "%s"', $path)
);
}
$dirSecured = self::secure_db_dir($path);
if (!$dirSecured) {
return array(
'success' => false,
'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path)
);
'success' => false,
'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path)
);
}
$conn = $this->createConnection($databaseConfig, $error);
$success = !empty($conn);
return array(
'success' => $success,
'connection' => $conn,
@ -140,7 +151,8 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
public function getDatabaseVersion($databaseConfig)
{
$version = 0;
/** @skipUpgrade */
switch ($databaseConfig['type']) {
case 'SQLite3Database':
$info = SQLite3::version();
@ -186,12 +198,12 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
'alreadyExists' => $alreadyExists,
);
}
/**
* Creates the provided directory and prepares it for
* storing SQLlite. Use {@link secure_db_dir()} to
* secure it against unauthorized access.
*
*
* @param String $path Absolute path, usually with a hidden folder.
* @return boolean
*/
@ -199,14 +211,14 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{
return file_exists($path) || mkdir($path);
}
/**
* Secure the provided directory via web-access
* by placing a .htaccess file in it.
* by placing a .htaccess file in it.
* This is just required if the database directory
* is placed within a publically accessible webroot (the
* default path is in a hidden folder within assets/).
*
*
* @param String $path Absolute path, containing a SQLite datatbase
* @return boolean
*/
@ -214,7 +226,7 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{
return (is_writeable($path)) ? file_put_contents($path . '/.htaccess', 'deny from all') : false;
}
public function requireDatabaseAlterPermissions($databaseConfig)
{
// no concept of table-specific permissions; If you can connect you can alter schema

View File

@ -1,7 +1,7 @@
{
"name": "silverstripe/sqlite3",
"description": "Adds SQLite3 support to SilverStripe",
"type": "silverstripe-module",
"type": "silverstripe-vendormodule",
"keywords": ["silverstripe", "sqlite3", "database"],
"authors": [
{
@ -14,16 +14,21 @@
}
],
"require": {
"silverstripe/framework": "~4.0"
"silverstripe/framework": "~4.0",
"silverstripe/vendor-plugin": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
"squizlabs/php_codesniffer": "^3"
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"autoload": {
"psr-4": {
"SilverStripe\\SQLite\\": "code/"
}
},
"scripts": {
"lint": "phpcs code/ *.php",
"lint-clean": "phpcbf code/ *.php"
},
"minimum-stability": "dev",
"prefer-stable": true
}

12
phpcs.xml.dist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>code</file>
<!-- base rules are PSR-2 -->
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
</rule>
</ruleset>