Problem/Motivation
Although the driver has means of autodetecting when statements have > 2100 parameters in order to enable EMULATE_PREPARES (as a workaround to deal with the different parameter limit in MySQL and SQL Server), this mechanism seems not to be working under some circumstances.
This autodetection method is in connection.php:
public function query($query, array $args = [], $options = []) {
// Use default values if not already set.
$options += $this->defaultOptions();
$stmt = NULL;
try {
// We allow either a pre-bound statement object or a literal string.
// In either case, we want to end up with an executed statement object,
// which we pass to PDOStatement::execute.
if ($query instanceof StatementInterface) {
$stmt = $query;
$stmt->execute(NULL, $options);
}
else {
$this->expandArguments($query, $args);
$insecure = isset($options['insecure']) ? $options['insecure'] : FALSE;
// Try to detect duplicate place holders, this check's performance
// is not a good addition to the driver, but does a good job preventing
// duplicate placeholder errors.
$argcount = count($args);
if ($insecure === TRUE || $argcount >= 2100 || ($argcount != substr_count($query, ':'))) {
$insecure = TRUE;
}
$stmt = $this->prepareQuery($query, array('insecure' => $insecure));
$stmt->execute($args, $options);
}
Obviously, when query is called with an already bound statement (i.e. the ones that come from other parts of the database abstraction layer such as MERGE, INSERT, etc.) it is unable to figure out the number of arguments in the statement and thus failing to turn on EMULATE_PREPARES.
Proposed resolution
The PDOStatement we are using is an external turbocharged implementation that keeps internal track of bound parameters and values. It *should* be easy (maybe even at a deeper level) to figure out in a more reliable way the number of arguments/params bound to a statement using the internal counter and turn on EMULATE_PREPARES accordingly.
Remaining tasks
Do it and test it.
User interface changes
None.
API changes
None.
Data model changes
None.
Original report
After saving a node with a large amount of field values I got the following error:
SQLSTATE[IMSSP]: Tried to bind parameter number 2101. SQL Server supports a maximum of 2100 parameters.: INSERT INTO
Complete error is attached in file SQLSRV-error-bind-parameter.txt.
This error is generated in the execute()
method in the class InsertQuery_sqlsrv
.
The $batch_size
doesn't check the maximum amount of 2100 parameters against the number of columns in the rows.
Comment | File | Size | Author |
---|---|---|---|
#17 | mssql_patch.txt | 2.19 KB | eugis |
#16 | mssql_patch.txt | 2.17 KB | eugis |
#4 | creating_node_sql_error-2779047-4.patch | 909 bytes | fabianvdbor |
SQLSRV-error-bind-parameter.txt | 40.43 KB | fabianvdbor |
Comments
Comment #2
fabianvdbor CreditAttribution: fabianvdbor as a volunteer and at Emble commentedThe attached patch calculate the maximum size of the batch.
Comment #3
fabianvdbor CreditAttribution: fabianvdbor as a volunteer and at Emble commentedComment #4
fabianvdbor CreditAttribution: fabianvdbor as a volunteer and at Emble commentedDuring the testing of my website I got this error with the bind parameters.
My code has calculated one batch size too much.
The attached patch fixes this.
Comment #5
david_garcia CreditAttribution: david_garcia commentedThanks for your patch. This is an interesting and useful approach.
I'll take care of this first in 8.x and then backport.
Comment #6
david_garcia CreditAttribution: david_garcia commentedThinking about this again... the driver has an automatic fallback to prevent the 2,100 parameter issues, where it will use EMULATE_PREPARES to trick the PDO driver. I'm not sure why this fallback is not triggering when doing inserts, needs to be investigated.
Comment #7
david_garcia CreditAttribution: david_garcia commentedComment #8
david_garcia CreditAttribution: david_garcia commentedComment #10
david_garcia CreditAttribution: david_garcia commentedMy original intention was to use EMULATE_PREPARES at a lower layer level relying on internal bound parameter count of the statement.
Unfortunately, statement is prepared before binding parameters so we need upstream code to decide to use emulate_prepares when needed, or to batch whatever it is doing.
As an experimental approach, I've added support for this in the MERGE statement implementation, that seems to be the most abused one.
Comment #11
eugis CreditAttribution: eugis commentedHi,
I'm also facing a similar issue when trying to import configs (drush cim).
Here's shortened error message:
Any possible solutions?
Comment #12
david_garcia CreditAttribution: david_garcia commented@eugis Is this with 8.x-1.x or 8.x-2.x?
Comment #13
eugis CreditAttribution: eugis commented@david_garcia
It's on 8.x-2.x. I've solved this by enabling wincache.
Comment #14
rajeevgoleI am also getting the same error, I tried the above solutions but none worked.
I am using Drupal 8.7.3 version.
In file docroot/drivers/lib/Drupal/Driver/Database/sqlsrv/Connection.php, I tried changing insecure options to true.
But then I started getting below error on every Drush command.
The error is "An error occurred translating the query string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page." in docroot/drivers/lib/Drupal/Driver/Database/sqlsrv/mssql/src/Statement.php:312
Any workaround for this?
Comment #15
rajeevgoleIn my case, I implemented the workaround by disable cache config. This is not a proper solution but at least I got unblocked.
In sites/default/services.yml
In settings.php
Comment #16
eugis CreditAttribution: eugis commentedRegarding the database caching - what about splitting cache array into chunks?
Comment #17
eugis CreditAttribution: eugis commented#16 is a bit wrong - here's another one.
Comment #18
david_garcia CreditAttribution: david_garcia commentedI made a new release that works around the parepared statement limit issue in Upsert statements also:
https://www.drupal.org/project/sqlsrv/releases/8.x-2.14
Comment #19
david_garcia CreditAttribution: david_garcia commentedI consider this fixed, please open a new issue relating to this one if any prepare limit issue shows somewhere else...