class MergeTest
Same name in this branch
- main core/tests/Drupal/KernelTests/Core/Database/MergeTest.php \Drupal\KernelTests\Core\Database\MergeTest
Same name and namespace in other branches
- 11.x core/tests/Drupal/KernelTests/Core/Database/MergeTest.php \Drupal\KernelTests\Core\Database\MergeTest
- 10 core/tests/Drupal/KernelTests/Core/Database/MergeTest.php \Drupal\KernelTests\Core\Database\MergeTest
- 9 core/tests/Drupal/KernelTests/Core/Database/MergeTest.php \Drupal\KernelTests\Core\Database\MergeTest
- 8.9.x core/tests/Drupal/KernelTests/Core/Database/MergeTest.php \Drupal\KernelTests\Core\Database\MergeTest
Tests the native MERGE implementation of the PostgreSQL driver.
The generic merge behavior is covered by the core MergeTest, which also runs against this driver. This test covers what is specific to the native MERGE implementation: a merge executes as a single MERGE statement, its status code is derived from RETURNING merge_action(), and queries that cannot be expressed as a native MERGE fall back to the generic emulation.
Attributes
#[Group('Database')]
#[RunTestsInSeparateProcesses]
Hierarchy
- class \Drupal\Tests\DrupalTestCase uses \Drupal\Tests\DrupalTestCaseTrait extends \PHPUnit\Framework\TestCase
- class \Drupal\KernelTests\KernelTestBase implements \Drupal\Core\DependencyInjection\ServiceProviderInterface uses \Drupal\KernelTests\AssertContentTrait, \Drupal\Tests\RandomGeneratorTrait, \Drupal\Tests\ConfigTestTrait, \Drupal\Tests\ExtensionListTestTrait, \Prophecy\PhpUnit\ProphecyTrait, \Drupal\Tests\BrowserHtmlDebugTrait, \Drupal\Tests\HttpKernelUiHelperTrait extends \Drupal\Tests\DrupalTestCase
- class \Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase extends \Drupal\KernelTests\KernelTestBase
- class \Drupal\KernelTests\Core\Database\DriverSpecificDatabaseTestBase uses \Drupal\KernelTests\Core\Database\DatabaseTestSchemaDataTrait, \Drupal\KernelTests\Core\Database\DatabaseTestSchemaInstallTrait extends \Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase
- class \Drupal\Tests\pgsql\Kernel\pgsql\MergeTest extends \Drupal\KernelTests\Core\Database\DriverSpecificDatabaseTestBase
- class \Drupal\KernelTests\Core\Database\DriverSpecificDatabaseTestBase uses \Drupal\KernelTests\Core\Database\DatabaseTestSchemaDataTrait, \Drupal\KernelTests\Core\Database\DatabaseTestSchemaInstallTrait extends \Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase
- class \Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase extends \Drupal\KernelTests\KernelTestBase
- class \Drupal\KernelTests\KernelTestBase implements \Drupal\Core\DependencyInjection\ServiceProviderInterface uses \Drupal\KernelTests\AssertContentTrait, \Drupal\Tests\RandomGeneratorTrait, \Drupal\Tests\ConfigTestTrait, \Drupal\Tests\ExtensionListTestTrait, \Prophecy\PhpUnit\ProphecyTrait, \Drupal\Tests\BrowserHtmlDebugTrait, \Drupal\Tests\HttpKernelUiHelperTrait extends \Drupal\Tests\DrupalTestCase
Expanded class hierarchy of MergeTest
File
-
core/
modules/ pgsql/ tests/ src/ Kernel/ pgsql/ MergeTest.php, line 22
Namespace
Drupal\Tests\pgsql\Kernel\pgsqlView source
class MergeTest extends DriverSpecificDatabaseTestBase {
/**
* Executes a merge-like callback and returns the logged queries.
*/
protected function getLoggedQueries(callable $callback) : array {
// Warm the table information cache outside of the logged region, so the
// log contains only the queries of the merge itself.
$this->connection
->schema()
->queryTableInformation('test_people');
Database::startLog('merge_test');
$callback();
$queries = Database::getLog('merge_test');
return array_column($queries, 'query');
}
/**
* Tests the SQL string of a native merge query.
*/
public function testMergeSqlString() : void {
$sql = (string) $this->connection
->merge('test_people')
->key('job', 'Speaker')
->fields([
'age' => 31,
'name' => 'Tiffany',
])
->useDefaults([
'job',
])
->expression('age', '[age] + :age', [
':age' => 4,
]);
$this->assertStringContainsString('MERGE INTO {test_people} USING (SELECT 1) AS drupal_merge_source ON', $sql);
$this->assertStringContainsString('WHEN MATCHED THEN UPDATE SET', $sql);
$this->assertStringContainsString('WHEN NOT MATCHED THEN INSERT', $sql);
$this->assertStringContainsString('DEFAULT', $sql);
$this->assertStringContainsString('RETURNING merge_action()', $sql);
}
/**
* Tests that a merge executes as a single native MERGE statement.
*/
public function testMergeExecutesSingleStatement() : void {
// Merge-insert: the key does not exist yet.
$result = NULL;
$queries = $this->getLoggedQueries(function () use (&$result) {
$result = $this->connection
->merge('test_people')
->key('job', 'Presenter')
->fields([
'age' => 31,
'name' => 'Tiffany',
])
->execute();
});
$this->assertSame(Merge::STATUS_INSERT, $result);
$this->assertCount(1, $queries);
$this->assertStringContainsString('MERGE INTO', $queries[0]);
// Merge-update: the same statement now matches the existing row.
$queries = $this->getLoggedQueries(function () use (&$result) {
$result = $this->connection
->merge('test_people')
->key('job', 'Presenter')
->fields([
'age' => 32,
'name' => 'Tiffany',
])
->execute();
});
$this->assertSame(Merge::STATUS_UPDATE, $result);
$this->assertCount(1, $queries);
$this->assertStringContainsString('MERGE INTO', $queries[0]);
$person = $this->connection
->query('SELECT * FROM {test_people} WHERE [job] = :job', [
':job' => 'Presenter',
])
->fetch();
$this->assertEquals('Tiffany', $person->name);
$this->assertEquals(32, $person->age);
}
/**
* Tests a merge that matches a row but has nothing to update.
*/
public function testMergeMatchedWithoutUpdate() : void {
$result = FALSE;
$queries = $this->getLoggedQueries(function () use (&$result) {
$result = $this->connection
->merge('test_people')
->key('job', 'Speaker')
->execute();
});
// Without update fields the MERGE has no WHEN MATCHED clause, so no
// action is performed and no status is returned.
$this->assertNull($result);
$this->assertCount(1, $queries);
$this->assertStringContainsString('MERGE INTO', $queries[0]);
$person = $this->connection
->query('SELECT * FROM {test_people} WHERE [job] = :job', [
':job' => 'Speaker',
])
->fetch();
$this->assertEquals('Meredith', $person->name);
$this->assertEquals(30, $person->age);
}
/**
* Tests a native merge combining literal updates and expressions.
*/
public function testMergeUpdateExpression() : void {
$result = $this->connection
->merge('test_people')
->key('job', 'Speaker')
->fields([
'name' => 'Tiffany',
])
->insertFields([
'age' => 31,
])
->expression('age', '[age] + :age', [
':age' => 4,
])
->execute();
$this->assertSame(Merge::STATUS_UPDATE, $result);
$person = $this->connection
->query('SELECT * FROM {test_people} WHERE [job] = :job', [
':job' => 'Speaker',
])
->fetch();
$this->assertEquals('Tiffany', $person->name);
$this->assertEquals(34, $person->age);
}
/**
* Tests a native merge with a select subquery as expression.
*/
public function testMergeSelectExpression() : void {
$select = $this->connection
->select('test', 't')
->condition('t.name', 'Ringo');
$select->addExpression('[t].[age]');
$result = $this->connection
->merge('test_people')
->key('job', 'Speaker')
->insertFields([
'name' => 'Tiffany',
])
->expression('age', $select)
->execute();
$this->assertSame(Merge::STATUS_UPDATE, $result);
$age = $this->connection
->query('SELECT [age] FROM {test_people} WHERE [job] = :job', [
':job' => 'Speaker',
])
->fetchField();
// Ringo's age in the sample data of the test table.
$this->assertEquals(28, $age);
}
/**
* Tests a native merge into blob fields.
*/
public function testMergeBlob() : void {
$this->connection
->schema()
->createTable('test_blob_merge', [
'fields' => [
'name' => [
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
],
'data' => [
'type' => 'blob',
'size' => 'big',
],
],
'primary key' => [
'name',
],
]);
$fetch_blob = function () {
$data = $this->connection
->query('SELECT [data] FROM {test_blob_merge} WHERE [name] = :name', [
':name' => 'trademark',
])
->fetchField();
return is_resource($data) ? stream_get_contents($data) : $data;
};
$binary = "Drupal \x00 The \x01 dries \xff his \xcc\xdd tears";
$result = $this->connection
->merge('test_blob_merge')
->key('name', 'trademark')
->fields([
'data' => $binary,
])
->execute();
$this->assertSame(Merge::STATUS_INSERT, $result);
$this->assertSame($binary, $fetch_blob());
$binary = "The \x00 quick \xcc brown \xff fox";
$result = $this->connection
->merge('test_blob_merge')
->key('name', 'trademark')
->fields([
'data' => $binary,
])
->execute();
$this->assertSame(Merge::STATUS_UPDATE, $result);
$this->assertSame($binary, $fetch_blob());
}
/**
* Tests that inserting into a serial field uses the generic emulation.
*/
public function testMergeSerialFieldFallback() : void {
$result = NULL;
$queries = $this->getLoggedQueries(function () use (&$result) {
$result = $this->connection
->merge('test')
->key('id', 999)
->fields([
'name' => 'David',
'age' => 40,
'job' => 'Developer',
])
->execute();
});
$this->assertSame(Merge::STATUS_INSERT, $result);
// The generic emulation issues a SELECT followed by an INSERT.
$this->assertGreaterThan(1, count($queries));
foreach ($queries as $query) {
$this->assertStringNotContainsString('MERGE INTO', $query);
}
// The insert path of the generic emulation has synchronized the sequence
// of the serial field, so a regular insert does not collide with the
// explicitly inserted id.
$id = $this->connection
->insert('test')
->fields([
'name' => 'Sylvia',
'age' => 41,
'job' => 'Editor',
])
->execute();
$this->assertEquals(1000, $id);
}
/**
* Tests that a merge without insert fields uses the generic emulation.
*/
public function testMergeWithoutInsertFieldsFallback() : void {
$result = NULL;
$queries = $this->getLoggedQueries(function () use (&$result) {
$result = $this->connection
->merge('test_people')
->condition('job', 'Speaker')
->updateFields([
'age' => 31,
])
->execute();
});
// Without insert fields a WHEN NOT MATCHED THEN INSERT clause cannot be
// generated, so the generic emulation is used.
$this->assertSame(Merge::STATUS_UPDATE, $result);
foreach ($queries as $query) {
$this->assertStringNotContainsString('MERGE INTO', $query);
}
$age = $this->connection
->query('SELECT [age] FROM {test_people} WHERE [job] = :job', [
':job' => 'Speaker',
])
->fetchField();
$this->assertEquals(31, $age);
}
}
Members
| Title Sort descending | Deprecated | Modifiers | Object type | Summary | Overriden Title | Overrides |
|---|---|---|---|---|---|---|
| AssertContentTrait::$content | protected | property | The current raw content. | |||
| AssertContentTrait::$drupalSettings | protected | property | The drupalSettings value from the current raw $content. | |||
| AssertContentTrait::$elements | protected | property | The XML structure parsed from the current raw $content. | |||
| AssertContentTrait::$plainTextContent | protected | property | The plain-text content of raw $content (text nodes). | |||
| AssertContentTrait::assertEscaped | protected | function | Passes if the raw text IS found escaped on the loaded page, fail otherwise. | |||
| AssertContentTrait::assertField | protected | function | Asserts that a field exists with the given name or ID. | |||
| AssertContentTrait::assertFieldByName | protected | function | Asserts that a field exists with the given name and value. | |||
| AssertContentTrait::assertFieldByXPath | protected | function | Asserts that a field exists in the current page by the given XPath. | |||
| AssertContentTrait::assertFieldsByValue | protected | function | Asserts that a field exists in the current page with a given Xpath result. | |||
| AssertContentTrait::assertLink | protected | function | Passes if a link with the specified label is found. | |||
| AssertContentTrait::assertLinkByHref | protected | function | Passes if a link containing a given href (part) is found. | |||
| AssertContentTrait::assertNoLink | protected | function | Passes if a link with the specified label is not found. | |||
| AssertContentTrait::assertNoPattern | protected | function | Triggers a pass if the perl regex pattern is not found in raw content. | |||
| AssertContentTrait::assertNoRaw | protected | function | Passes if the raw text is NOT found on the loaded page, fail otherwise. | |||
| AssertContentTrait::assertNoText | protected | function | Passes if the page (with HTML stripped) does not contains the text. | |||
| AssertContentTrait::assertPattern | protected | function | Triggers a pass if the Perl regex pattern is found in the raw content. | |||
| AssertContentTrait::assertRaw | protected | function | Passes if the raw text IS found on the loaded page, fail otherwise. | |||
| AssertContentTrait::assertText | protected | function | Passes if the page (with HTML stripped) contains the text. | |||
| AssertContentTrait::assertTextHelper | protected | function | Helper for assertText and assertNoText. | |||
| AssertContentTrait::assertThemeOutput | protected | function | Asserts themed output. | |||
| AssertContentTrait::assertTitle | protected | function | Pass if the page title is the given string. | |||
| AssertContentTrait::buildXPathQuery | protected | function | Builds an XPath query. | |||
| AssertContentTrait::constructFieldXpath | protected | function | Helper: Constructs an XPath for the given set of attributes and value. | |||
| AssertContentTrait::cssSelect | protected | function | Searches elements using a CSS selector in the raw content. | |||
| AssertContentTrait::getAllOptions | protected | function | Get all option elements, including nested options, in a select. | |||
| AssertContentTrait::getDrupalSettings | protected | function | Gets the value of drupalSettings for the currently-loaded page. | |||
| AssertContentTrait::getRawContent | protected | function | Gets the current raw content. | |||
| AssertContentTrait::getSelectedItem | protected | function | Get the selected value from a select field. | |||
| AssertContentTrait::getSimpleXmlElementsByXpath | protected | function | Performs an xpath search on the contents of the internal browser. | |||
| AssertContentTrait::getTextContent | protected | function | Retrieves the plain-text content from the current raw content. | |||
| AssertContentTrait::parse | protected | function | Parse content returned from curlExec using DOM and SimpleXML. | |||
| AssertContentTrait::removeWhiteSpace | protected | function | Removes all white-space between HTML tags from the raw content. | |||
| AssertContentTrait::setDrupalSettings | protected | function | Sets the value of drupalSettings for the currently-loaded page. | |||
| AssertContentTrait::setRawContent | protected | function | Sets the raw content (e.g. HTML). | |||
| AssertContentTrait::xpath | Deprecated | protected | function | Performs an xpath search on the contents of the internal browser. | ||
| BrowserHtmlDebugTrait::$htmlOutputBaseUrl | protected | property | The Base URI to use for links to the output files. | |||
| BrowserHtmlDebugTrait::$htmlOutputClassName | protected | property | Class name for HTML output logging. | |||
| BrowserHtmlDebugTrait::$htmlOutputCounter | protected | property | Counter for HTML output logging. | |||
| BrowserHtmlDebugTrait::$htmlOutputCounterStorage | protected | property | Counter storage for HTML output logging. | |||
| BrowserHtmlDebugTrait::$htmlOutputDirectory | protected | property | Directory name for HTML output logging. | |||
| BrowserHtmlDebugTrait::$htmlOutputEnabled | protected | property | HTML output enabled. | |||
| BrowserHtmlDebugTrait::$htmlOutputTestId | protected | property | HTML output test ID. | |||
| BrowserHtmlDebugTrait::formatHtmlOutputHeaders | protected | function | Formats HTTP headers as string for HTML output logging. | |||
| BrowserHtmlDebugTrait::getHtmlOutputHeaders | protected | function | Returns headers in HTML output format. | 1 | ||
| BrowserHtmlDebugTrait::getResponseLogHandler | protected | function | Provides a Guzzle middleware handler to log every response received. | |||
| BrowserHtmlDebugTrait::getTestMethodCaller | protected | function | Retrieves the current calling line in the class under test. | 1 | ||
| BrowserHtmlDebugTrait::htmlOutput | protected | function | Logs a HTML output message in a text file. | |||
| BrowserHtmlDebugTrait::initBrowserOutputFile | protected | function | Creates the directory to store browser output. | |||
| ConfigTestTrait::configImporter | protected | function | Returns a ConfigImporter object to import test configuration. | |||
| ConfigTestTrait::copyConfig | protected | function | Copies configuration objects from source storage to target storage. | |||
| DatabaseTestSchemaDataTrait::addSampleData | protected | function | Sets up our sample data. | |||
| DatabaseTestSchemaInstallTrait::installSampleSchema | protected | function | Sets up our sample table schema. | |||
| DriverSpecificDatabaseTestBase::$modules | protected static | property | Modules to install. | Overrides KernelTestBase::$modules | ||
| DriverSpecificDatabaseTestBase::setUp | protected | function | Overrides DriverSpecificKernelTestBase::setUp | |||
| DriverSpecificKernelTestBase::$connection | protected | property | The database connection for testing. | 1 | ||
| DrupalTestCase::$root | protected | property | The Drupal root directory. | |||
| DrupalTestCase::getDrupalRoot | Deprecated | protected | function | Returns the Drupal root directory. | 1 | |
| DrupalTestCase::setUpRoot | final protected | function | Ensure that the $root property is set initially. | |||
| DrupalTestCase::skipTestWithAttribute | final protected | function | Supports skipping tests with the Skip attribute. | |||
| DrupalTestCaseTrait::checkErrorHandlerOnTearDown | public | function | Checks the test error handler after test execution. | 1 | ||
| DrupalTestCaseTrait::expectExceptionMessageIs | protected | function | Expects an exactly matching exception message. | |||
| DrupalTestCaseTrait::expectExceptionMessageIsOrContains | protected | function | Expects an exception message containing a specified string. | |||
| DrupalTestCaseTrait::setDebugDumpHandler | public static | function | Registers the dumper CLI handler when the DebugDump extension is enabled. | |||
| ExtensionListTestTrait::getModulePath | protected | function | Gets the path for the specified module. | |||
| ExtensionListTestTrait::getThemePath | protected | function | Gets the path for the specified theme. | |||
| HttpKernelUiHelperTrait::$mink | protected | property | Mink session manager. | |||
| HttpKernelUiHelperTrait::assertSession | public | function | Returns WebAssert object. | |||
| HttpKernelUiHelperTrait::buildUrl | protected | function | Builds a URL from a system path or a URL object. | |||
| HttpKernelUiHelperTrait::clickLink | protected | function | Follows a link by complete name. | |||
| HttpKernelUiHelperTrait::drupalGet | protected | function | Retrieves a Drupal path. | |||
| HttpKernelUiHelperTrait::getDefaultDriverInstance | protected | function | Gets an instance of the default Mink driver. | |||
| HttpKernelUiHelperTrait::getNodeElementsByXpath | protected | function | Performs an xpath search on the contents of the internal browser. | |||
| HttpKernelUiHelperTrait::getSession | public | function | Returns Mink session. | |||
| HttpKernelUiHelperTrait::getUrl | protected | function | Gets the current URL from the browser. | |||
| HttpKernelUiHelperTrait::initMink | protected | function | Initializes Mink sessions. | |||
| HttpKernelUiHelperTrait::rebuildContainer | protected | function | Rebuilds the container. | |||
| KernelTestBase::$classLoader | protected | property | The class loader. | |||
| KernelTestBase::$configImporter | protected | property | The configuration importer. | |||
| KernelTestBase::$configSchemaCheckerExclusions | protected static | property | An array of config object names that are excluded from schema checking. | 4 | ||
| KernelTestBase::$container | protected | property | The test container. | |||
| KernelTestBase::$databasePrefix | protected | property | The test database prefix. | |||
| KernelTestBase::$keyValue | protected | property | The key_value service that must persist between container rebuilds. | |||
| KernelTestBase::$siteDirectory | protected | property | The relative path to the test site directory. | |||
| KernelTestBase::$strictConfigSchema | protected | property | Set to TRUE to strict check all configuration saved. | 9 | ||
| KernelTestBase::$usesSuperUserAccessPolicy | protected | property | Set to TRUE to make user 1 a super user. | 1 | ||
| KernelTestBase::$vfsRoot | protected | property | The virtual filesystem root directory. | |||
| KernelTestBase::assertPostConditions | protected | function | 1 | |||
| KernelTestBase::bootEnvironment | protected | function | Bootstraps a basic test environment. | |||
| KernelTestBase::bootKernel | protected | function | Bootstraps a kernel for a test. | 1 | ||
| KernelTestBase::config | protected | function | Configuration accessor for tests. Returns non-overridden configuration. | |||
| KernelTestBase::disableModules | protected | function | Disables modules for this test. | |||
| KernelTestBase::enableModules | protected | function | Enables modules for this test. | 2 | ||
| KernelTestBase::getConfigSchemaExclusions | protected | function | Gets the config schema exclusions for this test. | |||
| KernelTestBase::getDatabaseConnectionInfo | protected | function | Returns the Database connection info to be used for this test. | 3 | ||
| KernelTestBase::getDatabasePrefix | public | function | Gets the database prefix used for test isolation. | |||
| KernelTestBase::getExtensionsForModules | private | function | Returns Extension objects for $modules to install. | |||
| KernelTestBase::getModulesToEnable | protected static | function | Returns the modules to install for this test. | |||
| KernelTestBase::initFileCache | protected | function | Initializes the FileCache component. | |||
| KernelTestBase::installConfig | protected | function | Installs default configuration for a given list of modules. | |||
| KernelTestBase::installEntitySchema | protected | function | Installs the storage schema for a specific entity type. | |||
| KernelTestBase::installSchema | protected | function | Installs database tables from a module schema definition. | |||
| KernelTestBase::register | public | function | Registers test-specific services. | Overrides ServiceProviderInterface::register | 43 | |
| KernelTestBase::render | protected | function | Renders a render array. | 1 | ||
| KernelTestBase::setInstallProfile | protected | function | Sets the install profile and rebuilds the container to update it. | |||
| KernelTestBase::setSetting | protected | function | Sets an in-memory Settings variable. | |||
| KernelTestBase::setUpFilesystem | protected | function | Sets up the filesystem, so things like the file directory. | 3 | ||
| KernelTestBase::tearDown | protected | function | 11 | |||
| KernelTestBase::tearDownCloseDatabaseConnection | public | function | Additional tear down method to close the connection at the end. | |||
| KernelTestBase::vfsDump | protected | function | Dumps the current state of the virtual filesystem to STDOUT. | |||
| KernelTestBase::__sleep | public | function | Prevents serializing any properties. | |||
| MergeTest::getLoggedQueries | protected | function | Executes a merge-like callback and returns the logged queries. | |||
| MergeTest::testMergeBlob | public | function | Tests a native merge into blob fields. | |||
| MergeTest::testMergeExecutesSingleStatement | public | function | Tests that a merge executes as a single native MERGE statement. | |||
| MergeTest::testMergeMatchedWithoutUpdate | public | function | Tests a merge that matches a row but has nothing to update. | |||
| MergeTest::testMergeSelectExpression | public | function | Tests a native merge with a select subquery as expression. | |||
| MergeTest::testMergeSerialFieldFallback | public | function | Tests that inserting into a serial field uses the generic emulation. | |||
| MergeTest::testMergeSqlString | public | function | Tests the SQL string of a native merge query. | |||
| MergeTest::testMergeUpdateExpression | public | function | Tests a native merge combining literal updates and expressions. | |||
| MergeTest::testMergeWithoutInsertFieldsFallback | public | function | Tests that a merge without insert fields uses the generic emulation. | |||
| RandomGeneratorTrait::getRandomGenerator | protected | function | Gets the random generator for the utility methods. | |||
| RandomGeneratorTrait::randomMachineName | protected | function | Generates a unique random string containing letters and numbers. | |||
| RandomGeneratorTrait::randomObject | public | function | Generates a random PHP object. | |||
| RandomGeneratorTrait::randomString | public | function | Generates a pseudo-random string of ASCII characters of codes 32 to 126. | |||
| StorageCopyTrait::replaceStorageContents | protected static | function | Copy the configuration from one storage to another and remove stale items. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.