Skip to content

Commit

Permalink
issue #8, #17 create database containers for testing and update githu…
Browse files Browse the repository at this point in the history
…b actions to use database containers

issue #17 make postgres sequence counter update more robust
  • Loading branch information
bfinlay committed Jan 28, 2023
1 parent 2c70391 commit 3ce8045
Show file tree
Hide file tree
Showing 22 changed files with 532 additions and 37 deletions.
27 changes: 25 additions & 2 deletions .github/workflows/github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ["7.3", "7.4", "8.0", "8.1"]
php: ["7.3", "7.4", "8.0", "8.1", "8.2"]
laravel: [^5.8, ^6.0, ^7.0, ^8.0, ^9.0]
dbal: [^2.6, ^3.0]
exclude:
Expand All @@ -37,6 +37,12 @@ jobs:
laravel: ^6.0
- php: 8.1
laravel: ^7.0
- php: 8.2
laravel: ^5.8
- php: 8.2
laravel: ^6.0
- php: 8.2
laravel: ^7.0
- laravel: ^5.8
dbal: ^3.0
- laravel: ^6.0
Expand All @@ -57,4 +63,21 @@ jobs:
dependency-versions: "highest"
composer-options: "--with laravel/framework:${{ matrix.laravel }} --with doctrine/dbal:${{ matrix.dbal }}"

- run: vendor/bin/phpunit tests --cache-result-file=.phpunit.result.cache
- name: Create Database Containers
run: pwd;docker-compose up -d
- name: List Database Containers
run: docker-compose ps
- name: Wait for MySQL
run: |
while ! mysqladmin ping --host=127.0.0.1 --port=33068 --silent; do
sleep 1
done
- name: List Database Containers
run: docker-compose ps

- name: Run SQLite PHPUnit Unit Tests
run: vendor/bin/phpunit tests --cache-result-file=.phpunit.result.cache
- name: Run MySQL PHPUnit Unit Tests
run: vendor/bin/phpunit --configuration phpunit-mysql.xml tests --cache-result-file=.phpunit.result.cache
- name: Run Postgres PHPUnit Unit Tests
run: vendor/bin/phpunit --configuration phpunit-postgres.xml tests --cache-result-file=.phpunit.result.cache
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ composer.lock
.phpunit.result.cache
.*.tmp
~$*.xlsx
.DS_Store

examples/**/*.md
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
{
"type": "path",
"url": "../laravel-excel-seeder-test-data"
},
{
"type": "path",
"url": "~/development/laravel-expression-grammar"
}
],
"require-dev": {
"orchestra/testbench": "*",
"bfinlay/laravel-excel-seeder-test-data": "=2.3.1",
"symplify/easy-ci": "11.1.5"
"symplify/easy-ci": "11.1.5",
"angel-source-labs/laravel-expression-grammar": "*"
},
"autoload": {
"psr-4": {
Expand Down
8 changes: 5 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
# MYSQL_ROOT_PASSWORD: ''
# MYSQL_ALLOW_EMPTY_PASSWORD: 1
mysql80:
platform: linux/amd64
# platform: linux/amd64
image: mysql:8.0
ports:
- "33068:3306"
Expand Down Expand Up @@ -77,10 +77,12 @@ services:
# POSTGRES_PASSWORD: 'password'
## POSTGRES_HOST_AUTH_METHOD: 'trust'
sqlserver:
platform: linux/amd64
# platform: linux/arm64/v8
image: mcr.microsoft.com/azure-sql-edge
ports:
- "14330:1433"
environment:
ACCEPT_EULA: 'Y'
MSSQL_SA_PASSWORD: 'password'
MSSQL_SA_PASSWORD: 'MyPass@word'
MSSQL_PID: 'Developer'
MSSQL_USER: 'SA'
6 changes: 3 additions & 3 deletions examples/users.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name,email,email_verified_at,password
Foo,[email protected],2019-01-23 21:38:54,password
John,[email protected],2019-01-23 21:38:54,password
id,name,email,email_verified_at,password,order
1,Foo,[email protected],2019-01-23 21:38:54,password,10
5,John,[email protected],2019-01-23 21:38:54,password,3
35 changes: 35 additions & 0 deletions phpunit-sqlserver.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="tests">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlsrv"/>
<env name="DB_DATABASE" value=""/>
<env name="DB_HOST" value="127.0.0.1"/>
<env name="DB_PORT" value="14330"/>
<env name="DB_USERNAME" value="SA"/>
<env name="DB_PASSWORD" value="MyPass@word"/>

<!-- Set LARGE_ROWS_TESTS to "true" to run 15k and 100k tests -->
<env name="LARGE_ROWS_TESTS" value="false"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
5 changes: 5 additions & 0 deletions src/SpreadsheetSeederServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace bfinlay\SpreadsheetSeeder;

use bfinlay\SpreadsheetSeeder\Console\SeedCommand;
use bfinlay\SpreadsheetSeeder\Support\StrMacros;
use Illuminate\Database\Connection;
use Illuminate\Database\MySqlConnection;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;

class SpreadsheetSeederServiceProvider extends ServiceProvider
{
Expand Down Expand Up @@ -43,6 +45,9 @@ public function register()
$this->app->singleton(SeedCommand::class, function ($app) {
return new SeedCommand($app['db']);
});

if (!method_exists(Str::class, "beforeLast")) StrMacros::registerBeforeLastMacro();
if (!method_exists(Str::class, "between")) StrMacros::registerBetweenMacro();
}

/**
Expand Down
34 changes: 34 additions & 0 deletions src/Support/StrMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace bfinlay\SpreadsheetSeeder\Support;

use Illuminate\Support\Str;

class StrMacros
{
public static function registerBeforeLastMacro() {
Str::macro('beforeLast', function($subject, $search) {
if ($search === '') {
return $subject;
}

$pos = mb_strrpos($subject, $search);

if ($pos === false) {
return $subject;
}

return static::substr($subject, 0, $pos);
});
}

public static function registerBetweenMacro() {
Str::macro('between', function($subject, $from, $to) {
if ($from === '' || $to === '') {
return $subject;
}

return static::beforeLast(static::after($subject, $from), $to);
});
}
}
32 changes: 29 additions & 3 deletions src/Writers/Database/DatabaseWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Illuminate\Database\Query\Grammars\PostgresGrammar;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;

class DatabaseWriter
{
Expand Down Expand Up @@ -105,13 +106,38 @@ private function insertRows($rows)
}
}

public function updatePostgresSeqCounters($table) {
/**
* @param $table
* @return void
* @throws \Doctrine\DBAL\Exception
*/
public function updatePostgresSeqCounters($table)
{
if (!DB::connection()->getQueryGrammar() instanceof PostgresGrammar) {
return;
}

if (DB::connection()->getSchemaBuilder()->hasColumn($table, 'id')) {
$return = DB::select("select setval('{$table}_id_seq', max(id)) from {$table}");
foreach($this->getSequencesForTable($table) as $column => $sequence) {
$result = DB::select("select setval('{$sequence}', max(\"{$column}\")) from {$table}");
}
}

/**
* @param string $table
* @param string | string[] $columns
* @return \Doctrine\DBAL\Schema\Sequence[]
* @throws \Doctrine\DBAL\Exception
*/
public static function getSequencesForTable(string $table)
{
$sequences = DB::table('information_schema.columns')
->select("table_name", "column_name", "column_default")
->whereRaw("table_name=? and column_default like ?", [$table, "nextval%"])
->get()
->mapWithKeys(function ($value, $key) {
return [$value->column_name => Str::between($value->column_default, "'", "'")];
});

return $sequences;
}
}
25 changes: 23 additions & 2 deletions tests/ForeignKeyTruncateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace bfinlay\SpreadsheetSeeder\Tests;

use AngelSourceLabs\LaravelExpressionGrammar\ExpressionGrammar;
use bfinlay\SpreadsheetSeeder\Writers\Database\DestinationTable;
use bfinlay\SpreadsheetSeeder\SpreadsheetSeederSettings;
use bfinlay\SpreadsheetSeeder\Tests\Seeds\ForeignKeyTruncateTest\ForeignKeyTruncateSeeder;
use Illuminate\Support\Facades\DB;

class ForeignKeyTruncateTest extends TestCase
{
Expand All @@ -24,15 +26,27 @@ public function it_runs_the_migrations()
* Verify that truncating a table with a foreign key constraint throws a foreign key constraint exception
*
* @depends it_runs_the_migrations
*
* Postgres always truncates and cascades. See vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php compileTruncate(): truncate [table] restart identity cascade
*/
public function test_integrity_constraints_prevent_truncation()
{
if (DB::connection()->getDriverName() == "pgsql") $this->markTestSkipped('Test skipped for Postgres because Laravel always runs "truncate [table] restart identity cascade" for Postgres');
$this->seed(ForeignKeyTruncateSeeder::class);

/** @var $settings SpreadsheetSeederSettings */
$settings = app(SpreadsheetSeederSettings::class);
$settings->truncateIgnoreForeign = false;

$this->assertEquals(2, \DB::table('users')->count());
$this->assertEquals(2, \DB::table('favorite_numbers')->count());

$this->expectExceptionMessage('Integrity constraint violation: 19 FOREIGN KEY constraint failed');
$this->expectExceptionMessage(ExpressionGrammar::make()
->sqLite('Integrity constraint violation: 19 FOREIGN KEY constraint failed')
->mySql('Syntax error or access violation: 1701 Cannot truncate a table referenced in a foreign key constraint')
->postgres('Laravel always runs "truncate [table] restart identity cascade"')
->sqlServer('Cannot truncate table \'users\' because it is being referenced by a FOREIGN KEY constraint.')
);
\DB::table('users')->truncate();

$this->assertEquals(2, \DB::table('users')->count());
Expand All @@ -47,6 +61,7 @@ public function test_integrity_constraints_prevent_truncation()
*/
public function test_destination_table_truncation_observes_integrity_constraints()
{
if (DB::connection()->getDriverName() == "pgsql") $this->markTestSkipped('Test skipped for Postgres because Laravel always runs "truncate [table] restart identity cascade" for Postgres');
$this->seed(ForeignKeyTruncateSeeder::class);

$settings = resolve(SpreadsheetSeederSettings::class);
Expand All @@ -55,7 +70,12 @@ public function test_destination_table_truncation_observes_integrity_constraints
$this->assertEquals(2, \DB::table('users')->count());
$this->assertEquals(2, \DB::table('favorite_numbers')->count());

$this->expectExceptionMessage('Integrity constraint violation: 19 FOREIGN KEY constraint failed');
$this->expectExceptionMessage(ExpressionGrammar::make()
->sqLite('Integrity constraint violation: 19 FOREIGN KEY constraint failed')
->mySql('Syntax error or access violation: 1701 Cannot truncate a table referenced in a foreign key constraint')
->postgres('brion Syntax error or access violation: 1701 Cannot truncate a table referenced in a foreign key constraint')
->sqlServer('Cannot truncate table \'users\' because it is being referenced by a FOREIGN KEY constraint.')
);
$usersTable = new DestinationTable('users');

$this->assertEquals(0, \DB::table('users')->count());
Expand All @@ -81,6 +101,7 @@ public function test_destination_table_truncation_ignores_integrity_constraints(
$usersTable = new DestinationTable('users');

$this->assertEquals(0, \DB::table('users')->count());
$this->markTestIncomplete();
$this->assertEquals(2, \DB::table('favorite_numbers')->count());
}

Expand Down
18 changes: 18 additions & 0 deletions tests/Seeds/SequenceTest/Users2AccountSeeder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace bfinlay\SpreadsheetSeeder\Tests\Seeds\SequenceTest;

use bfinlay\SpreadsheetSeeder\SpreadsheetSeeder;
use bfinlay\SpreadsheetSeeder\SpreadsheetSeederSettings;
use bfinlay\SpreadsheetSeeder\Tests\Seeds\ParsersTest\UsersCsvParsersSeeder;

class Users2AccountSeeder extends UsersCsvParsersSeeder
{
public function settings(SpreadsheetSeederSettings $set)
{
parent::settings($set);
$set->tablename = 'users2_account';
$set->aliases = []; // settings are global singleton for request so need to be reset.
}
}
18 changes: 18 additions & 0 deletions tests/Seeds/SequenceTest/Users2Seeder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace bfinlay\SpreadsheetSeeder\Tests\Seeds\SequenceTest;

use bfinlay\SpreadsheetSeeder\SpreadsheetSeeder;
use bfinlay\SpreadsheetSeeder\SpreadsheetSeederSettings;
use bfinlay\SpreadsheetSeeder\Tests\Seeds\ParsersTest\UsersCsvParsersSeeder;

class Users2Seeder extends UsersCsvParsersSeeder
{
public function settings(SpreadsheetSeederSettings $set)
{
parent::settings($set);
$set->tablename = 'users2';
$set->aliases = ['id' => 'account_id'];
}
}
18 changes: 18 additions & 0 deletions tests/Seeds/SequenceTest/Users3Seeder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace bfinlay\SpreadsheetSeeder\Tests\Seeds\SequenceTest;

use bfinlay\SpreadsheetSeeder\SpreadsheetSeeder;
use bfinlay\SpreadsheetSeeder\SpreadsheetSeederSettings;
use bfinlay\SpreadsheetSeeder\Tests\Seeds\ParsersTest\UsersCsvParsersSeeder;

class Users3Seeder extends UsersCsvParsersSeeder
{
public function settings(SpreadsheetSeederSettings $set)
{
parent::settings($set);
$set->tablename = 'users3';
$set->aliases = []; // settings are global singleton for request so need to be reset.
}
}
Loading

0 comments on commit 3ce8045

Please sign in to comment.