Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
468af759
Commit
468af759
authored
Feb 13, 2012
by
Benjamin Eberlei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[DDC-450] Add TableGenerator that is safe from transaction failures.
parent
2b523bc4
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
307 additions
and
0 deletions
+307
-0
TableGenerator.php
lib/Doctrine/DBAL/Id/TableGenerator.php
+158
-0
TableGeneratorSchemaVisitor.php
lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php
+90
-0
TableGeneratorTest.php
tests/Doctrine/Tests/DBAL/Functional/TableGeneratorTest.php
+59
-0
No files found.
lib/Doctrine/DBAL/Id/TableGenerator.php
0 → 100644
View file @
468af759
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace
Doctrine\DBAL\Id
;
use
Doctrine\DBAL\DriverManager
;
use
Doctrine\DBAL\Connection
;
/**
* Table ID Generator for those poor languages that are missing sequences.
*
* WARNING: The Table Id Generator clones a second independent database
* connection to work correctly. This means using the generator requests that
* generate IDs will have two open database connections. This is necessary to
* be safe from transaction failures in the main connection. Make sure to only
* ever use one TableGenerator otherwise you end up with many connections.
*
* TableID Generator does not work with SQLite.
*
* The TableGenerator does not take care of creating the SQL Table itself. You
* should look at the `TableGeneratorSchemaVisitor` to do this for you.
* Otherwise the schema for a table looks like:
*
* CREATE sequences (
* sequence_name VARCHAR(255) NOT NULL,
* sequence_value INT NOT NULL DEFAULT '1',
* sequence_increment_by INT NOT NULL DEFAULT '1',
* PRIMARY KEY (table_name)
* );
*
* Technically this generator works as follows:
*
* 1. Use a robust transaction serialization level.
* 2. Open transaction
* 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE)
* 4. Increment current value by one and write back to database
* 5. Commit transaction
*
* If you are using a sequence_increment_by value that is larger than one the
* ID Generator will keep incrementing values until it hits the incrementation
* gap before issuing another query.
*
* If no row is present for a given sequence a new one will be created with the
* default values 'value' = 1 and 'increment_by' = 1
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class
TableGenerator
{
/**
* @var \Doctrine\DBAL\Connection
*/
private
$conn
;
/**
* @var string
*/
private
$generatorTableName
;
/**
* @var array
*/
private
$sequences
=
array
();
/**
* @param Connection $conn
* @param string $generatorTableName
*/
public
function
__construct
(
Connection
$conn
,
$generatorTableName
=
'sequences'
)
{
$params
=
$conn
->
getParams
();
if
(
$params
[
'driver'
]
==
'pdo_sqlite'
)
{
throw
new
\Doctrine\DBAL\DBALException
(
"Cannot use TableGenerator with SQLite."
);
}
$this
->
conn
=
DriverManager
::
getConnection
(
$params
,
$conn
->
getConfiguration
(),
$conn
->
getEventManager
());
$this
->
generatorTableName
=
$generatorTableName
;
}
/**
* Generate the next unused value for the given sequence name
*
* @param string
* @return int
*/
public
function
nextValue
(
$sequenceName
)
{
if
(
isset
(
$this
->
sequences
[
$sequenceName
]))
{
$value
=
$this
->
sequences
[
$sequenceName
][
'value'
];
$this
->
sequences
[
$sequenceName
][
'value'
]
++
;
if
(
$this
->
sequences
[
$sequenceName
][
'value'
]
>=
$this
->
sequences
[
$sequenceName
][
'max'
])
{
unset
(
$this
->
sequences
[
$sequenceName
]);
}
return
$value
;
}
$this
->
conn
->
beginTransaction
();
try
{
$platform
=
$this
->
conn
->
getDatabasePlatform
();
$sql
=
"SELECT sequence_value, sequence_increment_by "
.
"FROM "
.
$platform
->
appendLockHint
(
$this
->
generatorTableName
,
\Doctrine\DBAL\LockMode
::
PESSIMISTIC_WRITE
)
.
" "
.
"WHERE sequence_name = ? "
.
$platform
->
getWriteLockSQL
();
$stmt
=
$this
->
conn
->
executeQuery
(
$sql
,
array
(
$sequenceName
));
if
(
$row
=
$stmt
->
fetch
())
{
$value
=
$row
[
'sequence_value'
];
$value
++
;
if
(
$row
[
'sequence_increment_by'
]
>
1
)
{
$this
->
sequences
[
$sequenceName
]
=
array
(
'value'
=>
$value
,
'max'
=>
$row
[
'sequence_value'
]
+
$row
[
'sequence_increment_by'
]
);
}
$sql
=
"UPDATE "
.
$this
->
generatorTableName
.
" "
.
"SET sequence_value = sequence_value + sequence_increment_by "
.
"WHERE sequence_name = ? AND sequence_value = ?"
;
$rows
=
$this
->
conn
->
executeUpdate
(
$sql
,
array
(
$sequenceName
,
$row
[
'sequence_value'
]));
if
(
$rows
!=
1
)
{
throw
new
\Doctrine\DBAL\DBALException
(
"Race-condition detected while updating sequence. Aborting generation"
);
}
}
else
{
$this
->
conn
->
insert
(
$this
->
generatorTableName
,
array
(
'sequence_name'
=>
$sequenceName
,
'sequence_value'
=>
1
,
'sequence_increment_by'
=>
1
)
);
$value
=
1
;
}
$this
->
conn
->
commit
();
}
catch
(
\Exception
$e
)
{
$this
->
conn
->
rollback
();
throw
new
\Doctrine\DBAL\DBALException
(
"Error occured while generating ID with TableGenerator, aborted generation: "
.
$e
->
getMessage
(),
0
,
$e
);
}
return
$value
;
}
}
lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php
0 → 100644
View file @
468af759
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace
Doctrine\DBAL\Id
;
use
Doctrine\DBAL\Schema\Table
,
Doctrine\DBAL\Schema\Schema
,
Doctrine\DBAL\Schema\Column
,
Doctrine\DBAL\Schema\ForeignKeyConstraint
,
Doctrine\DBAL\Schema\Constraint
,
Doctrine\DBAL\Schema\Sequence
,
Doctrine\DBAL\Schema\Index
;
class
TableGeneratorSchemaVisitor
implements
\Doctrine\DBAL\Schema\Visitor\Visitor
{
/**
* @var string
*/
private
$generatorTableName
;
public
function
__construct
(
$generatorTableName
=
'sequences'
)
{
$this
->
generatorTableName
=
$generatorTableName
;
}
/**
* @param Schema $schema
*/
public
function
acceptSchema
(
Schema
$schema
)
{
$table
=
$schema
->
createTable
(
$this
->
generatorTableName
);
$table
->
addColumn
(
'sequence_name'
,
'string'
);
$table
->
addColumn
(
'sequence_value'
,
'integer'
,
array
(
'default'
=>
1
));
$table
->
addColumn
(
'sequence_increment_by'
,
'integer'
,
array
(
'default'
=>
1
));
}
/**
* @param Table $table
*/
public
function
acceptTable
(
Table
$table
)
{
}
/**
* @param Column $column
*/
public
function
acceptColumn
(
Table
$table
,
Column
$column
)
{
}
/**
* @param Table $localTable
* @param ForeignKeyConstraint $fkConstraint
*/
public
function
acceptForeignKey
(
Table
$localTable
,
ForeignKeyConstraint
$fkConstraint
)
{
}
/**
* @param Table $table
* @param Index $index
*/
public
function
acceptIndex
(
Table
$table
,
Index
$index
)
{
}
/**
* @param Sequence $sequence
*/
public
function
acceptSequence
(
Sequence
$sequence
)
{
}
}
tests/Doctrine/Tests/DBAL/Functional/TableGeneratorTest.php
0 → 100644
View file @
468af759
<?php
namespace
Doctrine\Tests\DBAL\Functional
;
use
Doctrine\DBAL\Id\TableGenerator
;
/**
* @group DDC-450
*/
class
TableGeneratorTest
extends
\Doctrine\Tests\DbalFunctionalTestCase
{
private
$generator
;
public
function
setUp
()
{
parent
::
setUp
();
$platform
=
$this
->
_conn
->
getDatabasePlatform
();
if
(
$platform
->
getName
()
==
"sqlite"
)
{
$this
->
markTestSkipped
(
'TableGenerator does not work with SQLite'
);
}
try
{
$schema
=
new
\Doctrine\DBAL\Schema\Schema
();
$visitor
=
new
\Doctrine\DBAL\Id\TableGeneratorSchemaVisitor
();
$schema
->
visit
(
$visitor
);
foreach
(
$schema
->
toSql
(
$platform
)
as
$sql
)
{
$this
->
_conn
->
exec
(
$sql
);
}
}
catch
(
\Exception
$e
)
{
}
$this
->
generator
=
new
TableGenerator
(
$this
->
_conn
);
}
public
function
testNextVal
()
{
$id1
=
$this
->
generator
->
nextValue
(
"tbl1"
);
$id2
=
$this
->
generator
->
nextValue
(
"tbl1"
);
$id3
=
$this
->
generator
->
nextValue
(
"tbl2"
);
$this
->
assertTrue
(
$id1
>
0
,
"First id has to be larger than 0"
);
$this
->
assertEquals
(
$id1
+
1
,
$id2
,
"Second id is one larger than first one."
);
$this
->
assertEquals
(
$id1
,
$id3
,
"First ids from different tables are equal."
);
}
public
function
testNextValNotAffectedByOuterTransactions
()
{
$this
->
_conn
->
beginTransaction
();
$id1
=
$this
->
generator
->
nextValue
(
"tbl1"
);
$this
->
_conn
->
rollBack
();
$id2
=
$this
->
generator
->
nextValue
(
"tbl1"
);
$this
->
assertTrue
(
$id1
>
0
,
"First id has to be larger than 0"
);
$this
->
assertEquals
(
$id1
+
1
,
$id2
,
"Second id is one larger than first one."
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment