Creating new DataObject results in database query with empty table and column names #139

Description

@leonixyz

Affected Version

{
"php": "^8.1",
"silverstripe/recipe-plugin": "~2.0.0@stable",
"silverstripe/vendor-plugin": "~2.0.0@stable",
"silverstripe/recipe-cms": "~5.0.0@stable",
"silverstripe/login-forms": "~5.0.0@stable",
"silverstripe/display-logic": "^3.0",
"undefinedoffset/sortablegridfield": "^2.2"
}

Description

A wrong query is issued to the database, having empty table name and empty column names.

UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3

If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

Peek.2024-05-11.23-32.mp4

Stack trace:

[Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
Source
55 if (!empty($sql)) {
56 $formatter = new SQLFormatter();
57 $formattedSQL = $formatter->formatPlain($sql);
58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
59 }
60 61 if ($errorLevel === E_USER_ERROR) {
62 // Treating errors as exceptions better allows for responding to errors
63 // in code, such as credential checking during installation
64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
65 } else {
66 user_error($msg ?? '', $errorLevel ?? 0);
67 }
68 }
69 70 /**
Trace
SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
MySQLiConnector.php:194
SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
Database.php:159
SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
Database.php:258
SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
Database.php:160
SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
MySQLDatabase.php:381
SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
DB.php:341
SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
GridFieldSortableRows.php:329
UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
GridFieldSortableRows.php:151
UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
GridField.php:411
SilverStripe\Forms\GridField\GridField->getManipulatedList()
GridFieldDetailForm_ItemRequest.php:573
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
GridFieldDetailForm_ItemRequest.php:654
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
GridFieldDetailForm_ItemRequest.php:682
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
GridFieldDetailForm_ItemRequest.php:334
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
GridFieldDetailForm_ItemRequest.php:420
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
VersionedGridFieldItemRequest.php:90
SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
GridFieldDetailForm_ItemRequest.php:242
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
GridFieldDetailForm_ItemRequest.php:160
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
RequestHandler.php:323
SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
RequestHandler.php:202
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
GridFieldDetailForm.php:149
SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
GridField.php:1237
SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
GridFieldDetailForm.php:149
SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
GridField.php:1237
SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
Controller.php:202
SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
LeftAndMain.php:799
SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
AdminRootController.php:124
SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
Director.php:349
SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
VersionedHTTPMiddleware.php:41
SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
LoginSessionMiddleware.php:53
SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ExecMetricMiddleware.php:20
SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ConfirmationMiddleware.php:254
SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ConfirmationMiddleware.php:254
SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
PasswordExpirationMiddleware.php:84
SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
BasicAuthMiddleware.php:68
SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AuthenticationMiddleware.php:61
SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
CanonicalURLMiddleware.php:245
SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPCacheControlMiddleware.php:41
SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ChangeDetectionMiddleware.php:28
SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
FlushMiddleware.php:31
SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
SessionMiddleware.php:20
SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AllowedHostsMiddleware.php:60
SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
TrustedProxyMiddleware.php:176
SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
Director.php:358
SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
HTTPApplication.php:114
SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
call_user_func(Closure, SilverStripe\Control\HTTPRequest)
HTTPApplication.php:137
SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
HTTPApplication.php:138
SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
HTTPApplication.php:113
SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
index.php:24

Steps to Reproduce

Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
└─────────────┘ │ │ │ │ │ │
│ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
┌──────────────┐ │ │1 *│ │* *│ │
│ │ ├─────────┘ └─────────────────────┘ └───────────────┘
│ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

Please forgive me if the code is redundant.

GridFieldConfig.php

<?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
publicstaticfunctioncreate(mixed ...$args) {
$config = GridFieldConfig_RelationEditor::create(50);
$config->addComponent(GridFieldSortableRows::create('SortOrder'));
return$config;
}
}

Attachments.php

<?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
useSilverStripe\Assets\File;
useSilverstripe\Forms\CheckboxField;
useSilverstripe\Forms\FieldList;
useSilverStripe\Forms\GridField\GridField;
useSilverStripe\Forms\TabSet;
useSilverstripe\Forms\TextField;
useSilverstripe\ORM\DataObject;
class Button extends DataObject {
privatestatic$db = [
'AdminOnly' => 'Boolean(0)',
'Description' => 'Text',
'Name' => 'Text',
];
privatestatic$has_many = [
'AttachmentsCategories' => AttachmentsCategory::class,
];
privatestatic$belongs_many_many = [
'Views' => View::class,
'MetricGroups' => MetricGroup::class,
];
privatestatic$searchable_fields = [
'Name',
'Description',
];
privatestatic$summary_fields = [
'Name' => 'Name',
'Description' => 'Description',
'IsAdminOnly' => 'Admin Only',
];
privatestatic$casting = [
'IsAdminOnly' => 'Text',
];
privatefunctionboolToText($input) {
if($input) {
return'yes';
} else {
return'no';
}
}
publicfunctiongetIsAdminOnly() {
return$this->boolToText($this->AdminOnly);
}
publicfunctionAttachmentsCategories() {
return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
}
publicfunctiongetCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
TextField::create('Name'),
TextField::create('Description'),
]);
$fields->addFieldsToTab('Root.Main', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->AttachmentsCategories(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
return$fields;
}
}
class AttachmentsCategory extends DataObject {
privatestatic$db = [
'Name' => 'Text',
'SortOrder' => 'Int',
];
privatestatic$has_one = [
'Button' => Button::class,
];
privatestatic$many_many = [
'Attachments' => Attachment::class,
];
privatestatic$many_many_extraFields = [
'Attachments' => [
'SortOrder' => 'Int',
],
];
publicfunctionAttachments() {
return$this->getManyManyComponents('Attachments')->sort('SortOrder');
}
publicfunctiongetCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
TextField::create('Name'),
]);
$fields->addFieldsToTab('Root.Main', [
GridField::create(
'Attachments',
'Attachments',
$this->Attachments(),
SortableGridFieldConfig::create(),
),
]);
return$fields;
}
}
class Attachment extends DataObject {
privatestatic$db = [
'Name' => 'Text',
];
privatestatic$has_one = [
'File' => File::class,
];
privatestatic$belongs_many_many = [
'Category' => AttachmentsCategory::class,
];
publicfunctiongetCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
TextField::create('Name'),
UploadField::create('File'),
]);
return$fields;
}
}

MetricGroup.php

<?phpuseSilverstripe\Forms\CheckboxField;
useSilverstripe\Forms\FieldList;
useSilverStripe\Forms\GridField\GridField;
useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
useSilverStripe\Forms\TabSet;
useSilverstripe\Forms\TextField;
useSilverstripe\ORM\DataObject;
useSilverStripe\Security\Security;
class MetricGroup extends DataObject {
privatestatic$db = [
'AdminOnly' => 'Boolean(0)',
'Description' => 'HTMLText',
'Enabled' => 'Boolean(1)',
'Subtitle' => 'Text',
'Title' => 'Text',
];
privatestatic$defaults = [
'Enabled' => true,
];
privatestatic$belongs_many_many = [
'View' => View::class,
];
privatestatic$many_many = [
'Buttons' => Button::class,
'Metrics' => Metric::class,
];
privatestatic$many_many_extraFields = [
'Metrics' => [
'SortOrder' => 'Int',
],
'Buttons' => [
'SortOrder' => 'Int',
],
];
privatestatic$summary_fields = [
'Title' => 'Title',
'Subtitle' => 'Subtitle',
'IsEnabled' => 'Enabled',
'IsAdminOnly' => 'Admin Only',
'MetricsCount' => 'Metrics',
];
privatestatic$casting = [
'IsEnabled' => 'Text',
'IsAdminOnly' => 'Text',
'MetricsCount' => 'Text',
];
privatefunctionboolToText($input) {
if($input) {
return'yes';
} else {
return'no';
}
}
publicfunctiongetIsEnabled() {
return$this->boolToText($this->Enabled);
}
publicfunctiongetIsAdminOnly() {
return$this->boolToText($this->AdminOnly);
}
publicfunctiongetMetricsCount() {
returncount($this->Metrics());
}
publicfunctionMetrics() {
$member = Security::getCurrentUser();
$metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$metrics = $metrics->filter(['AdminOnly' => false]);
}
return$metrics;
}
publicfunctionButtons() {
$member = Security::getCurrentUser();
$buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$buttons = $buttons->filter(['AdminOnly' => false]);
}
return$buttons;
}
publicfunctiongetCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('Enabled'),
CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
TextField::create('Title'),
TextField::create('Subtitle'),
HTMLEditorField::create('Description'),
GridField::create(
'Metrics',
'Metrics',
$this->Metrics(),
SortableGridFieldConfig::create()
->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
),
]);
$fields->addFieldsToTab('Root.Buttons', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->Buttons(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
$gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
return$fields;
}
}

View.php

<?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
useSilverStripe\Assets\File;
useSilverstripe\Forms\CheckboxField;
useSilverstripe\Forms\FieldList;
useSilverStripe\Forms\GridField\GridField;
useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
useSilverStripe\Forms\TabSet;
useSilverstripe\Forms\TextField;
useSilverstripe\ORM\DataObject;
useSilverStripe\Security\Member;
useSilverStripe\View\Parsers\ShortcodeParser;
useSilverStripe\Security\Security;
class View extends DataObject {
privatestatic$db = [
'Enabled' => 'Boolean(1)',
'Name' => 'Text',
'Code' => 'Text',
'Factory' => 'Text',
'Description' => 'HTMLText',
];
privatestatic$defaults = [
'Enabled' => true,
];
privatestatic$has_one = [
'Picture' => File::class,
'Graphic' => File::class,
];
privatestatic$belongs_many_many = [
'Owners' => Member::class,
];
privatestatic$many_many = [
'Buttons' => Button::class,
'MetricGroups' => MetricGroup::class,
];
privatestatic$many_many_extraFields = [
'MetricGroups' => [
'SortOrder' => 'Int',
],
'Buttons' => [
'SortOrder' => 'Int',
],
];
privatestatic$summary_fields = [
'Name' => 'Name',
'Code' => 'Code',
'IsEnabled' => 'Enabled',
];
privatestatic$casting = [
'IsEnabled' => 'Text',
];
privatefunctionboolToText($input) {
if($input) {
return'yes';
} else {
return'no';
}
}
publicfunctiongetIsEnabled() {
return$this->boolToText($this->Enabled);
}
publicfunctionMetricGroups() {
$member = Security::getCurrentUser();
$metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$metric_groups = $metric_groups->filter(['AdminOnly' => false]);
}
return$metric_groups;
}
publicfunctionButtons() {
$member = Security::getCurrentUser();
$buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$buttons = $buttons->filter(['AdminOnly' => false]);
}
return$buttons;
}
publicfunctiongetCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('Enabled'),
TextField::create('Name'),
TextField::create('Code'),
TextField::create('Factory'),
HTMLEditorField::create('Description'),
UploadField::create('Graphic', 'Graphic for main menu'),
UploadField::create('Picture', 'Synoptic'),
]);
$fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
'MetricGroups',
'Metric Groups',
$this->MetricGroups(),
SortableGridFieldConfig::create(),
),
]);
$fields->addFieldsToTab('Root.Buttons', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->Buttons(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
$gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
return$fields;
}
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Creating new DataObject results in database query with empty table and column names #139

      Description

      @leonixyz

      Affected Version

      {
      "php": "^8.1",
      "silverstripe/recipe-plugin": "~2.0.0@stable",
      "silverstripe/vendor-plugin": "~2.0.0@stable",
      "silverstripe/recipe-cms": "~5.0.0@stable",
      "silverstripe/login-forms": "~5.0.0@stable",
      "silverstripe/display-logic": "^3.0",
      "undefinedoffset/sortablegridfield": "^2.2"
      }

      Description

      A wrong query is issued to the database, having empty table name and empty column names.

      UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
      

      If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

      Peek.2024-05-11.23-32.mp4

      Stack trace:

      [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
      GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
      Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
      Source
      55 if (!empty($sql)) {
      56 $formatter = new SQLFormatter();
      57 $formattedSQL = $formatter->formatPlain($sql);
      58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
      59 }
      60 61 if ($errorLevel === E_USER_ERROR) {
      62 // Treating errors as exceptions better allows for responding to errors
      63 // in code, such as credential checking during installation
      64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
      65 } else {
      66 user_error($msg ?? '', $errorLevel ?? 0);
      67 }
      68 }
      69 70 /**
      Trace
      SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
      MySQLiConnector.php:194
      SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
      Database.php:159
      SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
      Database.php:258
      SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
      Database.php:160
      SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
      MySQLDatabase.php:381
      SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
      DB.php:341
      SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
      GridFieldSortableRows.php:329
      UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
      GridFieldSortableRows.php:151
      UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
      GridField.php:411
      SilverStripe\Forms\GridField\GridField->getManipulatedList()
      GridFieldDetailForm_ItemRequest.php:573
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
      GridFieldDetailForm_ItemRequest.php:654
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
      GridFieldDetailForm_ItemRequest.php:682
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
      GridFieldDetailForm_ItemRequest.php:334
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
      GridFieldDetailForm_ItemRequest.php:420
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
      VersionedGridFieldItemRequest.php:90
      SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
      GridFieldDetailForm_ItemRequest.php:242
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
      GridFieldDetailForm_ItemRequest.php:160
      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
      RequestHandler.php:323
      SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
      RequestHandler.php:202
      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
      GridFieldDetailForm.php:149
      SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
      GridField.php:1237
      SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
      RequestHandler.php:226
      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
      RequestHandler.php:226
      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
      GridFieldDetailForm.php:149
      SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
      GridField.php:1237
      SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
      RequestHandler.php:226
      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
      RequestHandler.php:226
      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
      Controller.php:202
      SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
      LeftAndMain.php:799
      SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
      AdminRootController.php:124
      SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
      Director.php:349
      SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
      VersionedHTTPMiddleware.php:41
      SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      LoginSessionMiddleware.php:53
      SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      ExecMetricMiddleware.php:20
      SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      ConfirmationMiddleware.php:254
      SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      ConfirmationMiddleware.php:254
      SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      PasswordExpirationMiddleware.php:84
      SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      BasicAuthMiddleware.php:68
      SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      AuthenticationMiddleware.php:61
      SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      CanonicalURLMiddleware.php:245
      SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      HTTPCacheControlMiddleware.php:41
      SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      ChangeDetectionMiddleware.php:28
      SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      FlushMiddleware.php:31
      SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      SessionMiddleware.php:20
      SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      AllowedHostsMiddleware.php:60
      SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      TrustedProxyMiddleware.php:176
      SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
      HTTPMiddlewareAware.php:62
      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
      HTTPMiddlewareAware.php:65
      SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
      Director.php:358
      SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
      HTTPApplication.php:114
      SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
      call_user_func(Closure, SilverStripe\Control\HTTPRequest)
      HTTPApplication.php:137
      SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
      HTTPMiddlewareAware.php:65
      SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
      HTTPApplication.php:138
      SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
      HTTPApplication.php:113
      SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
      index.php:24
      

      Steps to Reproduce

      Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

      ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
      └─────────────┘ │ │ │ │ │ │
      │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
      ┌──────────────┐ │ │1 *│ │* *│ │
      │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
      │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

      Please forgive me if the code is redundant.

      GridFieldConfig.php

      <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
      useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
      class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
      publicstaticfunctioncreate(mixed ...$args) {
      $config = GridFieldConfig_RelationEditor::create(50);
      $config->addComponent(GridFieldSortableRows::create('SortOrder'));
      return$config;
      }
      }

      Attachments.php

      <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
      useSilverStripe\Assets\File;
      useSilverstripe\Forms\CheckboxField;
      useSilverstripe\Forms\FieldList;
      useSilverStripe\Forms\GridField\GridField;
      useSilverStripe\Forms\TabSet;
      useSilverstripe\Forms\TextField;
      useSilverstripe\ORM\DataObject;
      class Button extends DataObject {
      privatestatic$db = [
      'AdminOnly' => 'Boolean(0)',
      'Description' => 'Text',
      'Name' => 'Text',
      ];
      privatestatic$has_many = [
      'AttachmentsCategories' => AttachmentsCategory::class,
      ];
      privatestatic$belongs_many_many = [
      'Views' => View::class,
      'MetricGroups' => MetricGroup::class,
      ];
      privatestatic$searchable_fields = [
      'Name',
      'Description',
      ];
      privatestatic$summary_fields = [
      'Name' => 'Name',
      'Description' => 'Description',
      'IsAdminOnly' => 'Admin Only',
      ];
      privatestatic$casting = [
      'IsAdminOnly' => 'Text',
      ];
      privatefunctionboolToText($input) {
      if($input) {
      return'yes';
      } else {
      return'no';
      }
      }
      publicfunctiongetIsAdminOnly() {
      return$this->boolToText($this->AdminOnly);
      }
      publicfunctionAttachmentsCategories() {
      return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
      }
      publicfunctiongetCMSfields() {
      $fields = FieldList::create(TabSet::create('Root'));
      $fields->addFieldsToTab('Root.Main', [
      CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
      TextField::create('Name'),
      TextField::create('Description'),
      ]);
      $fields->addFieldsToTab('Root.Main', [
      GridField::create(
      'AttachmentsCategories',
      'Attachments Categories',
      $this->AttachmentsCategories(),
      $gridConfig = SortableGridFieldConfig::create(),
      ),
      ]);
      return$fields;
      }
      }
      class AttachmentsCategory extends DataObject {
      privatestatic$db = [
      'Name' => 'Text',
      'SortOrder' => 'Int',
      ];
      privatestatic$has_one = [
      'Button' => Button::class,
      ];
      privatestatic$many_many = [
      'Attachments' => Attachment::class,
      ];
      privatestatic$many_many_extraFields = [
      'Attachments' => [
      'SortOrder' => 'Int',
      ],
      ];
      publicfunctionAttachments() {
      return$this->getManyManyComponents('Attachments')->sort('SortOrder');
      }
      publicfunctiongetCMSfields() {
      $fields = FieldList::create(TabSet::create('Root'));
      $fields->addFieldsToTab('Root.Main', [
      TextField::create('Name'),
      ]);
      $fields->addFieldsToTab('Root.Main', [
      GridField::create(
      'Attachments',
      'Attachments',
      $this->Attachments(),
      SortableGridFieldConfig::create(),
      ),
      ]);
      return$fields;
      }
      }
      class Attachment extends DataObject {
      privatestatic$db = [
      'Name' => 'Text',
      ];
      privatestatic$has_one = [
      'File' => File::class,
      ];
      privatestatic$belongs_many_many = [
      'Category' => AttachmentsCategory::class,
      ];
      publicfunctiongetCMSfields() {
      $fields = FieldList::create(TabSet::create('Root'));
      $fields->addFieldsToTab('Root.Main', [
      TextField::create('Name'),
      UploadField::create('File'),
      ]);
      return$fields;
      }
      }

      MetricGroup.php

      <?phpuseSilverstripe\Forms\CheckboxField;
      useSilverstripe\Forms\FieldList;
      useSilverStripe\Forms\GridField\GridField;
      useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
      useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
      useSilverStripe\Forms\TabSet;
      useSilverstripe\Forms\TextField;
      useSilverstripe\ORM\DataObject;
      useSilverStripe\Security\Security;
      class MetricGroup extends DataObject {
      privatestatic$db = [
      'AdminOnly' => 'Boolean(0)',
      'Description' => 'HTMLText',
      'Enabled' => 'Boolean(1)',
      'Subtitle' => 'Text',
      'Title' => 'Text',
      ];
      privatestatic$defaults = [
      'Enabled' => true,
      ];
      privatestatic$belongs_many_many = [
      'View' => View::class,
      ];
      privatestatic$many_many = [
      'Buttons' => Button::class,
      'Metrics' => Metric::class,
      ];
      privatestatic$many_many_extraFields = [
      'Metrics' => [
      'SortOrder' => 'Int',
      ],
      'Buttons' => [
      'SortOrder' => 'Int',
      ],
      ];
      privatestatic$summary_fields = [
      'Title' => 'Title',
      'Subtitle' => 'Subtitle',
      'IsEnabled' => 'Enabled',
      'IsAdminOnly' => 'Admin Only',
      'MetricsCount' => 'Metrics',
      ];
      privatestatic$casting = [
      'IsEnabled' => 'Text',
      'IsAdminOnly' => 'Text',
      'MetricsCount' => 'Text',
      ];
      privatefunctionboolToText($input) {
      if($input) {
      return'yes';
      } else {
      return'no';
      }
      }
      publicfunctiongetIsEnabled() {
      return$this->boolToText($this->Enabled);
      }
      publicfunctiongetIsAdminOnly() {
      return$this->boolToText($this->AdminOnly);
      }
      publicfunctiongetMetricsCount() {
      returncount($this->Metrics());
      }
      publicfunctionMetrics() {
      $member = Security::getCurrentUser();
      $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
      if (!$member->inGroup('administrators')) {
      $metrics = $metrics->filter(['AdminOnly' => false]);
      }
      return$metrics;
      }
      publicfunctionButtons() {
      $member = Security::getCurrentUser();
      $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
      if (!$member->inGroup('administrators')) {
      $buttons = $buttons->filter(['AdminOnly' => false]);
      }
      return$buttons;
      }
      publicfunctiongetCMSfields() {
      $fields = FieldList::create(TabSet::create('Root'));
      $fields->addFieldsToTab('Root.Main', [
      CheckboxField::create('Enabled'),
      CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
      TextField::create('Title'),
      TextField::create('Subtitle'),
      HTMLEditorField::create('Description'),
      GridField::create(
      'Metrics',
      'Metrics',
      $this->Metrics(),
      SortableGridFieldConfig::create()
      ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
      ),
      ]);
      $fields->addFieldsToTab('Root.Buttons', [
      GridField::create(
      'AttachmentsCategories',
      'Attachments Categories',
      $this->Buttons(),
      $gridConfig = SortableGridFieldConfig::create(),
      ),
      ]);
      $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
      return$fields;
      }
      }

      View.php

      <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
      useSilverStripe\Assets\File;
      useSilverstripe\Forms\CheckboxField;
      useSilverstripe\Forms\FieldList;
      useSilverStripe\Forms\GridField\GridField;
      useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
      useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
      useSilverStripe\Forms\TabSet;
      useSilverstripe\Forms\TextField;
      useSilverstripe\ORM\DataObject;
      useSilverStripe\Security\Member;
      useSilverStripe\View\Parsers\ShortcodeParser;
      useSilverStripe\Security\Security;
      class View extends DataObject {
      privatestatic$db = [
      'Enabled' => 'Boolean(1)',
      'Name' => 'Text',
      'Code' => 'Text',
      'Factory' => 'Text',
      'Description' => 'HTMLText',
      ];
      privatestatic$defaults = [
      'Enabled' => true,
      ];
      privatestatic$has_one = [
      'Picture' => File::class,
      'Graphic' => File::class,
      ];
      privatestatic$belongs_many_many = [
      'Owners' => Member::class,
      ];
      privatestatic$many_many = [
      'Buttons' => Button::class,
      'MetricGroups' => MetricGroup::class,
      ];
      privatestatic$many_many_extraFields = [
      'MetricGroups' => [
      'SortOrder' => 'Int',
      ],
      'Buttons' => [
      'SortOrder' => 'Int',
      ],
      ];
      privatestatic$summary_fields = [
      'Name' => 'Name',
      'Code' => 'Code',
      'IsEnabled' => 'Enabled',
      ];
      privatestatic$casting = [
      'IsEnabled' => 'Text',
      ];
      privatefunctionboolToText($input) {
      if($input) {
      return'yes';
      } else {
      return'no';
      }
      }
      publicfunctiongetIsEnabled() {
      return$this->boolToText($this->Enabled);
      }
      publicfunctionMetricGroups() {
      $member = Security::getCurrentUser();
      $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
      if (!$member->inGroup('administrators')) {
      $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
      }
      return$metric_groups;
      }
      publicfunctionButtons() {
      $member = Security::getCurrentUser();
      $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
      if (!$member->inGroup('administrators')) {
      $buttons = $buttons->filter(['AdminOnly' => false]);
      }
      return$buttons;
      }
      publicfunctiongetCMSfields() {
      $fields = FieldList::create(TabSet::create('Root'));
      $fields->addFieldsToTab('Root.Main', [
      CheckboxField::create('Enabled'),
      TextField::create('Name'),
      TextField::create('Code'),
      TextField::create('Factory'),
      HTMLEditorField::create('Description'),
      UploadField::create('Graphic', 'Graphic for main menu'),
      UploadField::create('Picture', 'Synoptic'),
      ]);
      $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
      'MetricGroups',
      'Metric Groups',
      $this->MetricGroups(),
      SortableGridFieldConfig::create(),
      ),
      ]);
      $fields->addFieldsToTab('Root.Buttons', [
      GridField::create(
      'AttachmentsCategories',
      'Attachments Categories',
      $this->Buttons(),
      $gridConfig = SortableGridFieldConfig::create(),
      ),
      ]);
      $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
      return$fields;
      }
      }

      Metadata

      Metadata

      Assignees

      No one assigned

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Creating new DataObject results in database query with empty table and column names #139

          Description

          @leonixyz

          Affected Version

          {
          "php": "^8.1",
          "silverstripe/recipe-plugin": "~2.0.0@stable",
          "silverstripe/vendor-plugin": "~2.0.0@stable",
          "silverstripe/recipe-cms": "~5.0.0@stable",
          "silverstripe/login-forms": "~5.0.0@stable",
          "silverstripe/display-logic": "^3.0",
          "undefinedoffset/sortablegridfield": "^2.2"
          }

          Description

          A wrong query is issued to the database, having empty table name and empty column names.

          UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
          

          If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

          Peek.2024-05-11.23-32.mp4

          Stack trace:

          [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
          GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
          Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
          Source
          55 if (!empty($sql)) {
          56 $formatter = new SQLFormatter();
          57 $formattedSQL = $formatter->formatPlain($sql);
          58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
          59 }
          60 61 if ($errorLevel === E_USER_ERROR) {
          62 // Treating errors as exceptions better allows for responding to errors
          63 // in code, such as credential checking during installation
          64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
          65 } else {
          66 user_error($msg ?? '', $errorLevel ?? 0);
          67 }
          68 }
          69 70 /**
          Trace
          SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
          MySQLiConnector.php:194
          SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
          Database.php:159
          SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
          Database.php:258
          SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
          Database.php:160
          SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
          MySQLDatabase.php:381
          SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
          DB.php:341
          SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
          GridFieldSortableRows.php:329
          UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
          GridFieldSortableRows.php:151
          UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
          GridField.php:411
          SilverStripe\Forms\GridField\GridField->getManipulatedList()
          GridFieldDetailForm_ItemRequest.php:573
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
          GridFieldDetailForm_ItemRequest.php:654
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
          GridFieldDetailForm_ItemRequest.php:682
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
          GridFieldDetailForm_ItemRequest.php:334
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
          GridFieldDetailForm_ItemRequest.php:420
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
          VersionedGridFieldItemRequest.php:90
          SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
          GridFieldDetailForm_ItemRequest.php:242
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
          GridFieldDetailForm_ItemRequest.php:160
          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
          RequestHandler.php:323
          SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
          RequestHandler.php:202
          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
          GridFieldDetailForm.php:149
          SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
          GridField.php:1237
          SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
          RequestHandler.php:226
          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
          RequestHandler.php:226
          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
          GridFieldDetailForm.php:149
          SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
          GridField.php:1237
          SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
          RequestHandler.php:226
          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
          RequestHandler.php:226
          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
          Controller.php:202
          SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
          LeftAndMain.php:799
          SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
          AdminRootController.php:124
          SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
          Director.php:349
          SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
          VersionedHTTPMiddleware.php:41
          SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          LoginSessionMiddleware.php:53
          SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          ExecMetricMiddleware.php:20
          SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          ConfirmationMiddleware.php:254
          SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          ConfirmationMiddleware.php:254
          SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          PasswordExpirationMiddleware.php:84
          SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          BasicAuthMiddleware.php:68
          SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          AuthenticationMiddleware.php:61
          SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          CanonicalURLMiddleware.php:245
          SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          HTTPCacheControlMiddleware.php:41
          SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          ChangeDetectionMiddleware.php:28
          SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          FlushMiddleware.php:31
          SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          SessionMiddleware.php:20
          SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          AllowedHostsMiddleware.php:60
          SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          TrustedProxyMiddleware.php:176
          SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
          HTTPMiddlewareAware.php:62
          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
          HTTPMiddlewareAware.php:65
          SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
          Director.php:358
          SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
          HTTPApplication.php:114
          SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
          call_user_func(Closure, SilverStripe\Control\HTTPRequest)
          HTTPApplication.php:137
          SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
          HTTPMiddlewareAware.php:65
          SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
          HTTPApplication.php:138
          SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
          HTTPApplication.php:113
          SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
          index.php:24
          

          Steps to Reproduce

          Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

          ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
          └─────────────┘ │ │ │ │ │ │
          │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
          ┌──────────────┐ │ │1 *│ │* *│ │
          │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
          │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

          Please forgive me if the code is redundant.

          GridFieldConfig.php

          <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
          useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
          class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
          publicstaticfunctioncreate(mixed ...$args) {
          $config = GridFieldConfig_RelationEditor::create(50);
          $config->addComponent(GridFieldSortableRows::create('SortOrder'));
          return$config;
          }
          }

          Attachments.php

          <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
          useSilverStripe\Assets\File;
          useSilverstripe\Forms\CheckboxField;
          useSilverstripe\Forms\FieldList;
          useSilverStripe\Forms\GridField\GridField;
          useSilverStripe\Forms\TabSet;
          useSilverstripe\Forms\TextField;
          useSilverstripe\ORM\DataObject;
          class Button extends DataObject {
          privatestatic$db = [
          'AdminOnly' => 'Boolean(0)',
          'Description' => 'Text',
          'Name' => 'Text',
          ];
          privatestatic$has_many = [
          'AttachmentsCategories' => AttachmentsCategory::class,
          ];
          privatestatic$belongs_many_many = [
          'Views' => View::class,
          'MetricGroups' => MetricGroup::class,
          ];
          privatestatic$searchable_fields = [
          'Name',
          'Description',
          ];
          privatestatic$summary_fields = [
          'Name' => 'Name',
          'Description' => 'Description',
          'IsAdminOnly' => 'Admin Only',
          ];
          privatestatic$casting = [
          'IsAdminOnly' => 'Text',
          ];
          privatefunctionboolToText($input) {
          if($input) {
          return'yes';
          } else {
          return'no';
          }
          }
          publicfunctiongetIsAdminOnly() {
          return$this->boolToText($this->AdminOnly);
          }
          publicfunctionAttachmentsCategories() {
          return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
          }
          publicfunctiongetCMSfields() {
          $fields = FieldList::create(TabSet::create('Root'));
          $fields->addFieldsToTab('Root.Main', [
          CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
          TextField::create('Name'),
          TextField::create('Description'),
          ]);
          $fields->addFieldsToTab('Root.Main', [
          GridField::create(
          'AttachmentsCategories',
          'Attachments Categories',
          $this->AttachmentsCategories(),
          $gridConfig = SortableGridFieldConfig::create(),
          ),
          ]);
          return$fields;
          }
          }
          class AttachmentsCategory extends DataObject {
          privatestatic$db = [
          'Name' => 'Text',
          'SortOrder' => 'Int',
          ];
          privatestatic$has_one = [
          'Button' => Button::class,
          ];
          privatestatic$many_many = [
          'Attachments' => Attachment::class,
          ];
          privatestatic$many_many_extraFields = [
          'Attachments' => [
          'SortOrder' => 'Int',
          ],
          ];
          publicfunctionAttachments() {
          return$this->getManyManyComponents('Attachments')->sort('SortOrder');
          }
          publicfunctiongetCMSfields() {
          $fields = FieldList::create(TabSet::create('Root'));
          $fields->addFieldsToTab('Root.Main', [
          TextField::create('Name'),
          ]);
          $fields->addFieldsToTab('Root.Main', [
          GridField::create(
          'Attachments',
          'Attachments',
          $this->Attachments(),
          SortableGridFieldConfig::create(),
          ),
          ]);
          return$fields;
          }
          }
          class Attachment extends DataObject {
          privatestatic$db = [
          'Name' => 'Text',
          ];
          privatestatic$has_one = [
          'File' => File::class,
          ];
          privatestatic$belongs_many_many = [
          'Category' => AttachmentsCategory::class,
          ];
          publicfunctiongetCMSfields() {
          $fields = FieldList::create(TabSet::create('Root'));
          $fields->addFieldsToTab('Root.Main', [
          TextField::create('Name'),
          UploadField::create('File'),
          ]);
          return$fields;
          }
          }

          MetricGroup.php

          <?phpuseSilverstripe\Forms\CheckboxField;
          useSilverstripe\Forms\FieldList;
          useSilverStripe\Forms\GridField\GridField;
          useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
          useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
          useSilverStripe\Forms\TabSet;
          useSilverstripe\Forms\TextField;
          useSilverstripe\ORM\DataObject;
          useSilverStripe\Security\Security;
          class MetricGroup extends DataObject {
          privatestatic$db = [
          'AdminOnly' => 'Boolean(0)',
          'Description' => 'HTMLText',
          'Enabled' => 'Boolean(1)',
          'Subtitle' => 'Text',
          'Title' => 'Text',
          ];
          privatestatic$defaults = [
          'Enabled' => true,
          ];
          privatestatic$belongs_many_many = [
          'View' => View::class,
          ];
          privatestatic$many_many = [
          'Buttons' => Button::class,
          'Metrics' => Metric::class,
          ];
          privatestatic$many_many_extraFields = [
          'Metrics' => [
          'SortOrder' => 'Int',
          ],
          'Buttons' => [
          'SortOrder' => 'Int',
          ],
          ];
          privatestatic$summary_fields = [
          'Title' => 'Title',
          'Subtitle' => 'Subtitle',
          'IsEnabled' => 'Enabled',
          'IsAdminOnly' => 'Admin Only',
          'MetricsCount' => 'Metrics',
          ];
          privatestatic$casting = [
          'IsEnabled' => 'Text',
          'IsAdminOnly' => 'Text',
          'MetricsCount' => 'Text',
          ];
          privatefunctionboolToText($input) {
          if($input) {
          return'yes';
          } else {
          return'no';
          }
          }
          publicfunctiongetIsEnabled() {
          return$this->boolToText($this->Enabled);
          }
          publicfunctiongetIsAdminOnly() {
          return$this->boolToText($this->AdminOnly);
          }
          publicfunctiongetMetricsCount() {
          returncount($this->Metrics());
          }
          publicfunctionMetrics() {
          $member = Security::getCurrentUser();
          $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
          if (!$member->inGroup('administrators')) {
          $metrics = $metrics->filter(['AdminOnly' => false]);
          }
          return$metrics;
          }
          publicfunctionButtons() {
          $member = Security::getCurrentUser();
          $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
          if (!$member->inGroup('administrators')) {
          $buttons = $buttons->filter(['AdminOnly' => false]);
          }
          return$buttons;
          }
          publicfunctiongetCMSfields() {
          $fields = FieldList::create(TabSet::create('Root'));
          $fields->addFieldsToTab('Root.Main', [
          CheckboxField::create('Enabled'),
          CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
          TextField::create('Title'),
          TextField::create('Subtitle'),
          HTMLEditorField::create('Description'),
          GridField::create(
          'Metrics',
          'Metrics',
          $this->Metrics(),
          SortableGridFieldConfig::create()
          ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
          ),
          ]);
          $fields->addFieldsToTab('Root.Buttons', [
          GridField::create(
          'AttachmentsCategories',
          'Attachments Categories',
          $this->Buttons(),
          $gridConfig = SortableGridFieldConfig::create(),
          ),
          ]);
          $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
          return$fields;
          }
          }

          View.php

          <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
          useSilverStripe\Assets\File;
          useSilverstripe\Forms\CheckboxField;
          useSilverstripe\Forms\FieldList;
          useSilverStripe\Forms\GridField\GridField;
          useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
          useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
          useSilverStripe\Forms\TabSet;
          useSilverstripe\Forms\TextField;
          useSilverstripe\ORM\DataObject;
          useSilverStripe\Security\Member;
          useSilverStripe\View\Parsers\ShortcodeParser;
          useSilverStripe\Security\Security;
          class View extends DataObject {
          privatestatic$db = [
          'Enabled' => 'Boolean(1)',
          'Name' => 'Text',
          'Code' => 'Text',
          'Factory' => 'Text',
          'Description' => 'HTMLText',
          ];
          privatestatic$defaults = [
          'Enabled' => true,
          ];
          privatestatic$has_one = [
          'Picture' => File::class,
          'Graphic' => File::class,
          ];
          privatestatic$belongs_many_many = [
          'Owners' => Member::class,
          ];
          privatestatic$many_many = [
          'Buttons' => Button::class,
          'MetricGroups' => MetricGroup::class,
          ];
          privatestatic$many_many_extraFields = [
          'MetricGroups' => [
          'SortOrder' => 'Int',
          ],
          'Buttons' => [
          'SortOrder' => 'Int',
          ],
          ];
          privatestatic$summary_fields = [
          'Name' => 'Name',
          'Code' => 'Code',
          'IsEnabled' => 'Enabled',
          ];
          privatestatic$casting = [
          'IsEnabled' => 'Text',
          ];
          privatefunctionboolToText($input) {
          if($input) {
          return'yes';
          } else {
          return'no';
          }
          }
          publicfunctiongetIsEnabled() {
          return$this->boolToText($this->Enabled);
          }
          publicfunctionMetricGroups() {
          $member = Security::getCurrentUser();
          $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
          if (!$member->inGroup('administrators')) {
          $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
          }
          return$metric_groups;
          }
          publicfunctionButtons() {
          $member = Security::getCurrentUser();
          $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
          if (!$member->inGroup('administrators')) {
          $buttons = $buttons->filter(['AdminOnly' => false]);
          }
          return$buttons;
          }
          publicfunctiongetCMSfields() {
          $fields = FieldList::create(TabSet::create('Root'));
          $fields->addFieldsToTab('Root.Main', [
          CheckboxField::create('Enabled'),
          TextField::create('Name'),
          TextField::create('Code'),
          TextField::create('Factory'),
          HTMLEditorField::create('Description'),
          UploadField::create('Graphic', 'Graphic for main menu'),
          UploadField::create('Picture', 'Synoptic'),
          ]);
          $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
          'MetricGroups',
          'Metric Groups',
          $this->MetricGroups(),
          SortableGridFieldConfig::create(),
          ),
          ]);
          $fields->addFieldsToTab('Root.Buttons', [
          GridField::create(
          'AttachmentsCategories',
          'Attachments Categories',
          $this->Buttons(),
          $gridConfig = SortableGridFieldConfig::create(),
          ),
          ]);
          $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
          return$fields;
          }
          }

          Metadata

          Metadata

          Assignees

          No one assigned

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Creating new DataObject results in database query with empty table and column names #139

              Description

              @leonixyz

              Affected Version

              {
              "php": "^8.1",
              "silverstripe/recipe-plugin": "~2.0.0@stable",
              "silverstripe/vendor-plugin": "~2.0.0@stable",
              "silverstripe/recipe-cms": "~5.0.0@stable",
              "silverstripe/login-forms": "~5.0.0@stable",
              "silverstripe/display-logic": "^3.0",
              "undefinedoffset/sortablegridfield": "^2.2"
              }

              Description

              A wrong query is issued to the database, having empty table name and empty column names.

              UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
              

              If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

              Peek.2024-05-11.23-32.mp4

              Stack trace:

              [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
              GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
              Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
              Source
              55 if (!empty($sql)) {
              56 $formatter = new SQLFormatter();
              57 $formattedSQL = $formatter->formatPlain($sql);
              58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
              59 }
              60 61 if ($errorLevel === E_USER_ERROR) {
              62 // Treating errors as exceptions better allows for responding to errors
              63 // in code, such as credential checking during installation
              64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
              65 } else {
              66 user_error($msg ?? '', $errorLevel ?? 0);
              67 }
              68 }
              69 70 /**
              Trace
              SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
              MySQLiConnector.php:194
              SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
              Database.php:159
              SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
              Database.php:258
              SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
              Database.php:160
              SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
              MySQLDatabase.php:381
              SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
              DB.php:341
              SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
              GridFieldSortableRows.php:329
              UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
              GridFieldSortableRows.php:151
              UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
              GridField.php:411
              SilverStripe\Forms\GridField\GridField->getManipulatedList()
              GridFieldDetailForm_ItemRequest.php:573
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
              GridFieldDetailForm_ItemRequest.php:654
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
              GridFieldDetailForm_ItemRequest.php:682
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
              GridFieldDetailForm_ItemRequest.php:334
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
              GridFieldDetailForm_ItemRequest.php:420
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
              VersionedGridFieldItemRequest.php:90
              SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
              GridFieldDetailForm_ItemRequest.php:242
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
              GridFieldDetailForm_ItemRequest.php:160
              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
              RequestHandler.php:323
              SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
              RequestHandler.php:202
              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
              GridFieldDetailForm.php:149
              SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
              GridField.php:1237
              SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
              RequestHandler.php:226
              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
              RequestHandler.php:226
              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
              GridFieldDetailForm.php:149
              SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
              GridField.php:1237
              SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
              RequestHandler.php:226
              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
              RequestHandler.php:226
              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
              Controller.php:202
              SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
              LeftAndMain.php:799
              SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
              AdminRootController.php:124
              SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
              Director.php:349
              SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
              VersionedHTTPMiddleware.php:41
              SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              LoginSessionMiddleware.php:53
              SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              ExecMetricMiddleware.php:20
              SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              ConfirmationMiddleware.php:254
              SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              ConfirmationMiddleware.php:254
              SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              PasswordExpirationMiddleware.php:84
              SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              BasicAuthMiddleware.php:68
              SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              AuthenticationMiddleware.php:61
              SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              CanonicalURLMiddleware.php:245
              SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              HTTPCacheControlMiddleware.php:41
              SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              ChangeDetectionMiddleware.php:28
              SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              FlushMiddleware.php:31
              SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              SessionMiddleware.php:20
              SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              AllowedHostsMiddleware.php:60
              SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              TrustedProxyMiddleware.php:176
              SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
              HTTPMiddlewareAware.php:62
              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
              HTTPMiddlewareAware.php:65
              SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
              Director.php:358
              SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
              HTTPApplication.php:114
              SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
              call_user_func(Closure, SilverStripe\Control\HTTPRequest)
              HTTPApplication.php:137
              SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
              HTTPMiddlewareAware.php:65
              SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
              HTTPApplication.php:138
              SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
              HTTPApplication.php:113
              SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
              index.php:24
              

              Steps to Reproduce

              Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

              ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
              └─────────────┘ │ │ │ │ │ │
              │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
              ┌──────────────┐ │ │1 *│ │* *│ │
              │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
              │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

              Please forgive me if the code is redundant.

              GridFieldConfig.php

              <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
              useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
              class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
              publicstaticfunctioncreate(mixed ...$args) {
              $config = GridFieldConfig_RelationEditor::create(50);
              $config->addComponent(GridFieldSortableRows::create('SortOrder'));
              return$config;
              }
              }

              Attachments.php

              <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
              useSilverStripe\Assets\File;
              useSilverstripe\Forms\CheckboxField;
              useSilverstripe\Forms\FieldList;
              useSilverStripe\Forms\GridField\GridField;
              useSilverStripe\Forms\TabSet;
              useSilverstripe\Forms\TextField;
              useSilverstripe\ORM\DataObject;
              class Button extends DataObject {
              privatestatic$db = [
              'AdminOnly' => 'Boolean(0)',
              'Description' => 'Text',
              'Name' => 'Text',
              ];
              privatestatic$has_many = [
              'AttachmentsCategories' => AttachmentsCategory::class,
              ];
              privatestatic$belongs_many_many = [
              'Views' => View::class,
              'MetricGroups' => MetricGroup::class,
              ];
              privatestatic$searchable_fields = [
              'Name',
              'Description',
              ];
              privatestatic$summary_fields = [
              'Name' => 'Name',
              'Description' => 'Description',
              'IsAdminOnly' => 'Admin Only',
              ];
              privatestatic$casting = [
              'IsAdminOnly' => 'Text',
              ];
              privatefunctionboolToText($input) {
              if($input) {
              return'yes';
              } else {
              return'no';
              }
              }
              publicfunctiongetIsAdminOnly() {
              return$this->boolToText($this->AdminOnly);
              }
              publicfunctionAttachmentsCategories() {
              return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
              }
              publicfunctiongetCMSfields() {
              $fields = FieldList::create(TabSet::create('Root'));
              $fields->addFieldsToTab('Root.Main', [
              CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
              TextField::create('Name'),
              TextField::create('Description'),
              ]);
              $fields->addFieldsToTab('Root.Main', [
              GridField::create(
              'AttachmentsCategories',
              'Attachments Categories',
              $this->AttachmentsCategories(),
              $gridConfig = SortableGridFieldConfig::create(),
              ),
              ]);
              return$fields;
              }
              }
              class AttachmentsCategory extends DataObject {
              privatestatic$db = [
              'Name' => 'Text',
              'SortOrder' => 'Int',
              ];
              privatestatic$has_one = [
              'Button' => Button::class,
              ];
              privatestatic$many_many = [
              'Attachments' => Attachment::class,
              ];
              privatestatic$many_many_extraFields = [
              'Attachments' => [
              'SortOrder' => 'Int',
              ],
              ];
              publicfunctionAttachments() {
              return$this->getManyManyComponents('Attachments')->sort('SortOrder');
              }
              publicfunctiongetCMSfields() {
              $fields = FieldList::create(TabSet::create('Root'));
              $fields->addFieldsToTab('Root.Main', [
              TextField::create('Name'),
              ]);
              $fields->addFieldsToTab('Root.Main', [
              GridField::create(
              'Attachments',
              'Attachments',
              $this->Attachments(),
              SortableGridFieldConfig::create(),
              ),
              ]);
              return$fields;
              }
              }
              class Attachment extends DataObject {
              privatestatic$db = [
              'Name' => 'Text',
              ];
              privatestatic$has_one = [
              'File' => File::class,
              ];
              privatestatic$belongs_many_many = [
              'Category' => AttachmentsCategory::class,
              ];
              publicfunctiongetCMSfields() {
              $fields = FieldList::create(TabSet::create('Root'));
              $fields->addFieldsToTab('Root.Main', [
              TextField::create('Name'),
              UploadField::create('File'),
              ]);
              return$fields;
              }
              }

              MetricGroup.php

              <?phpuseSilverstripe\Forms\CheckboxField;
              useSilverstripe\Forms\FieldList;
              useSilverStripe\Forms\GridField\GridField;
              useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
              useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
              useSilverStripe\Forms\TabSet;
              useSilverstripe\Forms\TextField;
              useSilverstripe\ORM\DataObject;
              useSilverStripe\Security\Security;
              class MetricGroup extends DataObject {
              privatestatic$db = [
              'AdminOnly' => 'Boolean(0)',
              'Description' => 'HTMLText',
              'Enabled' => 'Boolean(1)',
              'Subtitle' => 'Text',
              'Title' => 'Text',
              ];
              privatestatic$defaults = [
              'Enabled' => true,
              ];
              privatestatic$belongs_many_many = [
              'View' => View::class,
              ];
              privatestatic$many_many = [
              'Buttons' => Button::class,
              'Metrics' => Metric::class,
              ];
              privatestatic$many_many_extraFields = [
              'Metrics' => [
              'SortOrder' => 'Int',
              ],
              'Buttons' => [
              'SortOrder' => 'Int',
              ],
              ];
              privatestatic$summary_fields = [
              'Title' => 'Title',
              'Subtitle' => 'Subtitle',
              'IsEnabled' => 'Enabled',
              'IsAdminOnly' => 'Admin Only',
              'MetricsCount' => 'Metrics',
              ];
              privatestatic$casting = [
              'IsEnabled' => 'Text',
              'IsAdminOnly' => 'Text',
              'MetricsCount' => 'Text',
              ];
              privatefunctionboolToText($input) {
              if($input) {
              return'yes';
              } else {
              return'no';
              }
              }
              publicfunctiongetIsEnabled() {
              return$this->boolToText($this->Enabled);
              }
              publicfunctiongetIsAdminOnly() {
              return$this->boolToText($this->AdminOnly);
              }
              publicfunctiongetMetricsCount() {
              returncount($this->Metrics());
              }
              publicfunctionMetrics() {
              $member = Security::getCurrentUser();
              $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
              if (!$member->inGroup('administrators')) {
              $metrics = $metrics->filter(['AdminOnly' => false]);
              }
              return$metrics;
              }
              publicfunctionButtons() {
              $member = Security::getCurrentUser();
              $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
              if (!$member->inGroup('administrators')) {
              $buttons = $buttons->filter(['AdminOnly' => false]);
              }
              return$buttons;
              }
              publicfunctiongetCMSfields() {
              $fields = FieldList::create(TabSet::create('Root'));
              $fields->addFieldsToTab('Root.Main', [
              CheckboxField::create('Enabled'),
              CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
              TextField::create('Title'),
              TextField::create('Subtitle'),
              HTMLEditorField::create('Description'),
              GridField::create(
              'Metrics',
              'Metrics',
              $this->Metrics(),
              SortableGridFieldConfig::create()
              ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
              ),
              ]);
              $fields->addFieldsToTab('Root.Buttons', [
              GridField::create(
              'AttachmentsCategories',
              'Attachments Categories',
              $this->Buttons(),
              $gridConfig = SortableGridFieldConfig::create(),
              ),
              ]);
              $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
              return$fields;
              }
              }

              View.php

              <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
              useSilverStripe\Assets\File;
              useSilverstripe\Forms\CheckboxField;
              useSilverstripe\Forms\FieldList;
              useSilverStripe\Forms\GridField\GridField;
              useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
              useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
              useSilverStripe\Forms\TabSet;
              useSilverstripe\Forms\TextField;
              useSilverstripe\ORM\DataObject;
              useSilverStripe\Security\Member;
              useSilverStripe\View\Parsers\ShortcodeParser;
              useSilverStripe\Security\Security;
              class View extends DataObject {
              privatestatic$db = [
              'Enabled' => 'Boolean(1)',
              'Name' => 'Text',
              'Code' => 'Text',
              'Factory' => 'Text',
              'Description' => 'HTMLText',
              ];
              privatestatic$defaults = [
              'Enabled' => true,
              ];
              privatestatic$has_one = [
              'Picture' => File::class,
              'Graphic' => File::class,
              ];
              privatestatic$belongs_many_many = [
              'Owners' => Member::class,
              ];
              privatestatic$many_many = [
              'Buttons' => Button::class,
              'MetricGroups' => MetricGroup::class,
              ];
              privatestatic$many_many_extraFields = [
              'MetricGroups' => [
              'SortOrder' => 'Int',
              ],
              'Buttons' => [
              'SortOrder' => 'Int',
              ],
              ];
              privatestatic$summary_fields = [
              'Name' => 'Name',
              'Code' => 'Code',
              'IsEnabled' => 'Enabled',
              ];
              privatestatic$casting = [
              'IsEnabled' => 'Text',
              ];
              privatefunctionboolToText($input) {
              if($input) {
              return'yes';
              } else {
              return'no';
              }
              }
              publicfunctiongetIsEnabled() {
              return$this->boolToText($this->Enabled);
              }
              publicfunctionMetricGroups() {
              $member = Security::getCurrentUser();
              $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
              if (!$member->inGroup('administrators')) {
              $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
              }
              return$metric_groups;
              }
              publicfunctionButtons() {
              $member = Security::getCurrentUser();
              $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
              if (!$member->inGroup('administrators')) {
              $buttons = $buttons->filter(['AdminOnly' => false]);
              }
              return$buttons;
              }
              publicfunctiongetCMSfields() {
              $fields = FieldList::create(TabSet::create('Root'));
              $fields->addFieldsToTab('Root.Main', [
              CheckboxField::create('Enabled'),
              TextField::create('Name'),
              TextField::create('Code'),
              TextField::create('Factory'),
              HTMLEditorField::create('Description'),
              UploadField::create('Graphic', 'Graphic for main menu'),
              UploadField::create('Picture', 'Synoptic'),
              ]);
              $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
              'MetricGroups',
              'Metric Groups',
              $this->MetricGroups(),
              SortableGridFieldConfig::create(),
              ),
              ]);
              $fields->addFieldsToTab('Root.Buttons', [
              GridField::create(
              'AttachmentsCategories',
              'Attachments Categories',
              $this->Buttons(),
              $gridConfig = SortableGridFieldConfig::create(),
              ),
              ]);
              $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
              return$fields;
              }
              }

              Metadata

              Metadata

              Assignees

              No one assigned

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Creating new DataObject results in database query with empty table and column names #139

                  Description

                  @leonixyz

                  Affected Version

                  {
                  "php": "^8.1",
                  "silverstripe/recipe-plugin": "~2.0.0@stable",
                  "silverstripe/vendor-plugin": "~2.0.0@stable",
                  "silverstripe/recipe-cms": "~5.0.0@stable",
                  "silverstripe/login-forms": "~5.0.0@stable",
                  "silverstripe/display-logic": "^3.0",
                  "undefinedoffset/sortablegridfield": "^2.2"
                  }

                  Description

                  A wrong query is issued to the database, having empty table name and empty column names.

                  UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
                  

                  If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

                  Peek.2024-05-11.23-32.mp4

                  Stack trace:

                  [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
                  GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
                  Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
                  Source
                  55 if (!empty($sql)) {
                  56 $formatter = new SQLFormatter();
                  57 $formattedSQL = $formatter->formatPlain($sql);
                  58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
                  59 }
                  60 61 if ($errorLevel === E_USER_ERROR) {
                  62 // Treating errors as exceptions better allows for responding to errors
                  63 // in code, such as credential checking during installation
                  64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
                  65 } else {
                  66 user_error($msg ?? '', $errorLevel ?? 0);
                  67 }
                  68 }
                  69 70 /**
                  Trace
                  SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                  MySQLiConnector.php:194
                  SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                  Database.php:159
                  SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                  Database.php:258
                  SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
                  Database.php:160
                  SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                  MySQLDatabase.php:381
                  SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                  DB.php:341
                  SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                  GridFieldSortableRows.php:329
                  UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                  GridFieldSortableRows.php:151
                  UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                  GridField.php:411
                  SilverStripe\Forms\GridField\GridField->getManipulatedList()
                  GridFieldDetailForm_ItemRequest.php:573
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
                  GridFieldDetailForm_ItemRequest.php:654
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
                  GridFieldDetailForm_ItemRequest.php:682
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
                  GridFieldDetailForm_ItemRequest.php:334
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
                  GridFieldDetailForm_ItemRequest.php:420
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
                  VersionedGridFieldItemRequest.php:90
                  SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
                  GridFieldDetailForm_ItemRequest.php:242
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
                  GridFieldDetailForm_ItemRequest.php:160
                  SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
                  RequestHandler.php:323
                  SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
                  RequestHandler.php:202
                  SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                  GridFieldDetailForm.php:149
                  SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                  GridField.php:1237
                  SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                  RequestHandler.php:226
                  SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                  RequestHandler.php:226
                  SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                  GridFieldDetailForm.php:149
                  SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                  GridField.php:1237
                  SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                  RequestHandler.php:226
                  SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                  RequestHandler.php:226
                  SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                  Controller.php:202
                  SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
                  LeftAndMain.php:799
                  SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
                  AdminRootController.php:124
                  SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
                  Director.php:349
                  SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                  VersionedHTTPMiddleware.php:41
                  SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  LoginSessionMiddleware.php:53
                  SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  ExecMetricMiddleware.php:20
                  SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  ConfirmationMiddleware.php:254
                  SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  ConfirmationMiddleware.php:254
                  SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  PasswordExpirationMiddleware.php:84
                  SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  BasicAuthMiddleware.php:68
                  SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  AuthenticationMiddleware.php:61
                  SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  CanonicalURLMiddleware.php:245
                  SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  HTTPCacheControlMiddleware.php:41
                  SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  ChangeDetectionMiddleware.php:28
                  SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  FlushMiddleware.php:31
                  SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  SessionMiddleware.php:20
                  SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  AllowedHostsMiddleware.php:60
                  SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  TrustedProxyMiddleware.php:176
                  SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPMiddlewareAware.php:62
                  SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                  HTTPMiddlewareAware.php:65
                  SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                  Director.php:358
                  SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
                  HTTPApplication.php:114
                  SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                  call_user_func(Closure, SilverStripe\Control\HTTPRequest)
                  HTTPApplication.php:137
                  SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                  HTTPMiddlewareAware.php:65
                  SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                  HTTPApplication.php:138
                  SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
                  HTTPApplication.php:113
                  SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
                  index.php:24
                  

                  Steps to Reproduce

                  Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

                  ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
                  └─────────────┘ │ │ │ │ │ │
                  │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
                  ┌──────────────┐ │ │1 *│ │* *│ │
                  │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
                  │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

                  Please forgive me if the code is redundant.

                  GridFieldConfig.php

                  <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
                  useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
                  class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
                  publicstaticfunctioncreate(mixed ...$args) {
                  $config = GridFieldConfig_RelationEditor::create(50);
                  $config->addComponent(GridFieldSortableRows::create('SortOrder'));
                  return$config;
                  }
                  }

                  Attachments.php

                  <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                  useSilverStripe\Assets\File;
                  useSilverstripe\Forms\CheckboxField;
                  useSilverstripe\Forms\FieldList;
                  useSilverStripe\Forms\GridField\GridField;
                  useSilverStripe\Forms\TabSet;
                  useSilverstripe\Forms\TextField;
                  useSilverstripe\ORM\DataObject;
                  class Button extends DataObject {
                  privatestatic$db = [
                  'AdminOnly' => 'Boolean(0)',
                  'Description' => 'Text',
                  'Name' => 'Text',
                  ];
                  privatestatic$has_many = [
                  'AttachmentsCategories' => AttachmentsCategory::class,
                  ];
                  privatestatic$belongs_many_many = [
                  'Views' => View::class,
                  'MetricGroups' => MetricGroup::class,
                  ];
                  privatestatic$searchable_fields = [
                  'Name',
                  'Description',
                  ];
                  privatestatic$summary_fields = [
                  'Name' => 'Name',
                  'Description' => 'Description',
                  'IsAdminOnly' => 'Admin Only',
                  ];
                  privatestatic$casting = [
                  'IsAdminOnly' => 'Text',
                  ];
                  privatefunctionboolToText($input) {
                  if($input) {
                  return'yes';
                  } else {
                  return'no';
                  }
                  }
                  publicfunctiongetIsAdminOnly() {
                  return$this->boolToText($this->AdminOnly);
                  }
                  publicfunctionAttachmentsCategories() {
                  return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
                  }
                  publicfunctiongetCMSfields() {
                  $fields = FieldList::create(TabSet::create('Root'));
                  $fields->addFieldsToTab('Root.Main', [
                  CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                  TextField::create('Name'),
                  TextField::create('Description'),
                  ]);
                  $fields->addFieldsToTab('Root.Main', [
                  GridField::create(
                  'AttachmentsCategories',
                  'Attachments Categories',
                  $this->AttachmentsCategories(),
                  $gridConfig = SortableGridFieldConfig::create(),
                  ),
                  ]);
                  return$fields;
                  }
                  }
                  class AttachmentsCategory extends DataObject {
                  privatestatic$db = [
                  'Name' => 'Text',
                  'SortOrder' => 'Int',
                  ];
                  privatestatic$has_one = [
                  'Button' => Button::class,
                  ];
                  privatestatic$many_many = [
                  'Attachments' => Attachment::class,
                  ];
                  privatestatic$many_many_extraFields = [
                  'Attachments' => [
                  'SortOrder' => 'Int',
                  ],
                  ];
                  publicfunctionAttachments() {
                  return$this->getManyManyComponents('Attachments')->sort('SortOrder');
                  }
                  publicfunctiongetCMSfields() {
                  $fields = FieldList::create(TabSet::create('Root'));
                  $fields->addFieldsToTab('Root.Main', [
                  TextField::create('Name'),
                  ]);
                  $fields->addFieldsToTab('Root.Main', [
                  GridField::create(
                  'Attachments',
                  'Attachments',
                  $this->Attachments(),
                  SortableGridFieldConfig::create(),
                  ),
                  ]);
                  return$fields;
                  }
                  }
                  class Attachment extends DataObject {
                  privatestatic$db = [
                  'Name' => 'Text',
                  ];
                  privatestatic$has_one = [
                  'File' => File::class,
                  ];
                  privatestatic$belongs_many_many = [
                  'Category' => AttachmentsCategory::class,
                  ];
                  publicfunctiongetCMSfields() {
                  $fields = FieldList::create(TabSet::create('Root'));
                  $fields->addFieldsToTab('Root.Main', [
                  TextField::create('Name'),
                  UploadField::create('File'),
                  ]);
                  return$fields;
                  }
                  }

                  MetricGroup.php

                  <?phpuseSilverstripe\Forms\CheckboxField;
                  useSilverstripe\Forms\FieldList;
                  useSilverStripe\Forms\GridField\GridField;
                  useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                  useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                  useSilverStripe\Forms\TabSet;
                  useSilverstripe\Forms\TextField;
                  useSilverstripe\ORM\DataObject;
                  useSilverStripe\Security\Security;
                  class MetricGroup extends DataObject {
                  privatestatic$db = [
                  'AdminOnly' => 'Boolean(0)',
                  'Description' => 'HTMLText',
                  'Enabled' => 'Boolean(1)',
                  'Subtitle' => 'Text',
                  'Title' => 'Text',
                  ];
                  privatestatic$defaults = [
                  'Enabled' => true,
                  ];
                  privatestatic$belongs_many_many = [
                  'View' => View::class,
                  ];
                  privatestatic$many_many = [
                  'Buttons' => Button::class,
                  'Metrics' => Metric::class,
                  ];
                  privatestatic$many_many_extraFields = [
                  'Metrics' => [
                  'SortOrder' => 'Int',
                  ],
                  'Buttons' => [
                  'SortOrder' => 'Int',
                  ],
                  ];
                  privatestatic$summary_fields = [
                  'Title' => 'Title',
                  'Subtitle' => 'Subtitle',
                  'IsEnabled' => 'Enabled',
                  'IsAdminOnly' => 'Admin Only',
                  'MetricsCount' => 'Metrics',
                  ];
                  privatestatic$casting = [
                  'IsEnabled' => 'Text',
                  'IsAdminOnly' => 'Text',
                  'MetricsCount' => 'Text',
                  ];
                  privatefunctionboolToText($input) {
                  if($input) {
                  return'yes';
                  } else {
                  return'no';
                  }
                  }
                  publicfunctiongetIsEnabled() {
                  return$this->boolToText($this->Enabled);
                  }
                  publicfunctiongetIsAdminOnly() {
                  return$this->boolToText($this->AdminOnly);
                  }
                  publicfunctiongetMetricsCount() {
                  returncount($this->Metrics());
                  }
                  publicfunctionMetrics() {
                  $member = Security::getCurrentUser();
                  $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
                  if (!$member->inGroup('administrators')) {
                  $metrics = $metrics->filter(['AdminOnly' => false]);
                  }
                  return$metrics;
                  }
                  publicfunctionButtons() {
                  $member = Security::getCurrentUser();
                  $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                  if (!$member->inGroup('administrators')) {
                  $buttons = $buttons->filter(['AdminOnly' => false]);
                  }
                  return$buttons;
                  }
                  publicfunctiongetCMSfields() {
                  $fields = FieldList::create(TabSet::create('Root'));
                  $fields->addFieldsToTab('Root.Main', [
                  CheckboxField::create('Enabled'),
                  CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                  TextField::create('Title'),
                  TextField::create('Subtitle'),
                  HTMLEditorField::create('Description'),
                  GridField::create(
                  'Metrics',
                  'Metrics',
                  $this->Metrics(),
                  SortableGridFieldConfig::create()
                  ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
                  ),
                  ]);
                  $fields->addFieldsToTab('Root.Buttons', [
                  GridField::create(
                  'AttachmentsCategories',
                  'Attachments Categories',
                  $this->Buttons(),
                  $gridConfig = SortableGridFieldConfig::create(),
                  ),
                  ]);
                  $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                  return$fields;
                  }
                  }

                  View.php

                  <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                  useSilverStripe\Assets\File;
                  useSilverstripe\Forms\CheckboxField;
                  useSilverstripe\Forms\FieldList;
                  useSilverStripe\Forms\GridField\GridField;
                  useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                  useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                  useSilverStripe\Forms\TabSet;
                  useSilverstripe\Forms\TextField;
                  useSilverstripe\ORM\DataObject;
                  useSilverStripe\Security\Member;
                  useSilverStripe\View\Parsers\ShortcodeParser;
                  useSilverStripe\Security\Security;
                  class View extends DataObject {
                  privatestatic$db = [
                  'Enabled' => 'Boolean(1)',
                  'Name' => 'Text',
                  'Code' => 'Text',
                  'Factory' => 'Text',
                  'Description' => 'HTMLText',
                  ];
                  privatestatic$defaults = [
                  'Enabled' => true,
                  ];
                  privatestatic$has_one = [
                  'Picture' => File::class,
                  'Graphic' => File::class,
                  ];
                  privatestatic$belongs_many_many = [
                  'Owners' => Member::class,
                  ];
                  privatestatic$many_many = [
                  'Buttons' => Button::class,
                  'MetricGroups' => MetricGroup::class,
                  ];
                  privatestatic$many_many_extraFields = [
                  'MetricGroups' => [
                  'SortOrder' => 'Int',
                  ],
                  'Buttons' => [
                  'SortOrder' => 'Int',
                  ],
                  ];
                  privatestatic$summary_fields = [
                  'Name' => 'Name',
                  'Code' => 'Code',
                  'IsEnabled' => 'Enabled',
                  ];
                  privatestatic$casting = [
                  'IsEnabled' => 'Text',
                  ];
                  privatefunctionboolToText($input) {
                  if($input) {
                  return'yes';
                  } else {
                  return'no';
                  }
                  }
                  publicfunctiongetIsEnabled() {
                  return$this->boolToText($this->Enabled);
                  }
                  publicfunctionMetricGroups() {
                  $member = Security::getCurrentUser();
                  $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
                  if (!$member->inGroup('administrators')) {
                  $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
                  }
                  return$metric_groups;
                  }
                  publicfunctionButtons() {
                  $member = Security::getCurrentUser();
                  $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                  if (!$member->inGroup('administrators')) {
                  $buttons = $buttons->filter(['AdminOnly' => false]);
                  }
                  return$buttons;
                  }
                  publicfunctiongetCMSfields() {
                  $fields = FieldList::create(TabSet::create('Root'));
                  $fields->addFieldsToTab('Root.Main', [
                  CheckboxField::create('Enabled'),
                  TextField::create('Name'),
                  TextField::create('Code'),
                  TextField::create('Factory'),
                  HTMLEditorField::create('Description'),
                  UploadField::create('Graphic', 'Graphic for main menu'),
                  UploadField::create('Picture', 'Synoptic'),
                  ]);
                  $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
                  'MetricGroups',
                  'Metric Groups',
                  $this->MetricGroups(),
                  SortableGridFieldConfig::create(),
                  ),
                  ]);
                  $fields->addFieldsToTab('Root.Buttons', [
                  GridField::create(
                  'AttachmentsCategories',
                  'Attachments Categories',
                  $this->Buttons(),
                  $gridConfig = SortableGridFieldConfig::create(),
                  ),
                  ]);
                  $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                  return$fields;
                  }
                  }

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Creating new DataObject results in database query with empty table and column names #139

                      Description

                      @leonixyz

                      Affected Version

                      {
                      "php": "^8.1",
                      "silverstripe/recipe-plugin": "~2.0.0@stable",
                      "silverstripe/vendor-plugin": "~2.0.0@stable",
                      "silverstripe/recipe-cms": "~5.0.0@stable",
                      "silverstripe/login-forms": "~5.0.0@stable",
                      "silverstripe/display-logic": "^3.0",
                      "undefinedoffset/sortablegridfield": "^2.2"
                      }

                      Description

                      A wrong query is issued to the database, having empty table name and empty column names.

                      UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
                      

                      If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

                      Peek.2024-05-11.23-32.mp4

                      Stack trace:

                      [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
                      GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
                      Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
                      Source
                      55 if (!empty($sql)) {
                      56 $formatter = new SQLFormatter();
                      57 $formattedSQL = $formatter->formatPlain($sql);
                      58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
                      59 }
                      60 61 if ($errorLevel === E_USER_ERROR) {
                      62 // Treating errors as exceptions better allows for responding to errors
                      63 // in code, such as credential checking during installation
                      64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
                      65 } else {
                      66 user_error($msg ?? '', $errorLevel ?? 0);
                      67 }
                      68 }
                      69 70 /**
                      Trace
                      SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                      MySQLiConnector.php:194
                      SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                      Database.php:159
                      SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                      Database.php:258
                      SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
                      Database.php:160
                      SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                      MySQLDatabase.php:381
                      SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                      DB.php:341
                      SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                      GridFieldSortableRows.php:329
                      UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                      GridFieldSortableRows.php:151
                      UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                      GridField.php:411
                      SilverStripe\Forms\GridField\GridField->getManipulatedList()
                      GridFieldDetailForm_ItemRequest.php:573
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
                      GridFieldDetailForm_ItemRequest.php:654
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
                      GridFieldDetailForm_ItemRequest.php:682
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
                      GridFieldDetailForm_ItemRequest.php:334
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
                      GridFieldDetailForm_ItemRequest.php:420
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
                      VersionedGridFieldItemRequest.php:90
                      SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
                      GridFieldDetailForm_ItemRequest.php:242
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
                      GridFieldDetailForm_ItemRequest.php:160
                      SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
                      RequestHandler.php:323
                      SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
                      RequestHandler.php:202
                      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                      GridFieldDetailForm.php:149
                      SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                      GridField.php:1237
                      SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                      RequestHandler.php:226
                      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                      RequestHandler.php:226
                      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                      GridFieldDetailForm.php:149
                      SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                      GridField.php:1237
                      SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                      RequestHandler.php:226
                      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                      RequestHandler.php:226
                      SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                      Controller.php:202
                      SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
                      LeftAndMain.php:799
                      SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
                      AdminRootController.php:124
                      SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
                      Director.php:349
                      SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                      VersionedHTTPMiddleware.php:41
                      SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      LoginSessionMiddleware.php:53
                      SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      ExecMetricMiddleware.php:20
                      SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      ConfirmationMiddleware.php:254
                      SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      ConfirmationMiddleware.php:254
                      SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      PasswordExpirationMiddleware.php:84
                      SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      BasicAuthMiddleware.php:68
                      SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      AuthenticationMiddleware.php:61
                      SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      CanonicalURLMiddleware.php:245
                      SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      HTTPCacheControlMiddleware.php:41
                      SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      ChangeDetectionMiddleware.php:28
                      SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      FlushMiddleware.php:31
                      SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      SessionMiddleware.php:20
                      SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      AllowedHostsMiddleware.php:60
                      SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      TrustedProxyMiddleware.php:176
                      SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPMiddlewareAware.php:62
                      SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                      HTTPMiddlewareAware.php:65
                      SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                      Director.php:358
                      SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
                      HTTPApplication.php:114
                      SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                      call_user_func(Closure, SilverStripe\Control\HTTPRequest)
                      HTTPApplication.php:137
                      SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                      HTTPMiddlewareAware.php:65
                      SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                      HTTPApplication.php:138
                      SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
                      HTTPApplication.php:113
                      SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
                      index.php:24
                      

                      Steps to Reproduce

                      Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

                      ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
                      └─────────────┘ │ │ │ │ │ │
                      │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
                      ┌──────────────┐ │ │1 *│ │* *│ │
                      │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
                      │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

                      Please forgive me if the code is redundant.

                      GridFieldConfig.php

                      <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
                      useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
                      class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
                      publicstaticfunctioncreate(mixed ...$args) {
                      $config = GridFieldConfig_RelationEditor::create(50);
                      $config->addComponent(GridFieldSortableRows::create('SortOrder'));
                      return$config;
                      }
                      }

                      Attachments.php

                      <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                      useSilverStripe\Assets\File;
                      useSilverstripe\Forms\CheckboxField;
                      useSilverstripe\Forms\FieldList;
                      useSilverStripe\Forms\GridField\GridField;
                      useSilverStripe\Forms\TabSet;
                      useSilverstripe\Forms\TextField;
                      useSilverstripe\ORM\DataObject;
                      class Button extends DataObject {
                      privatestatic$db = [
                      'AdminOnly' => 'Boolean(0)',
                      'Description' => 'Text',
                      'Name' => 'Text',
                      ];
                      privatestatic$has_many = [
                      'AttachmentsCategories' => AttachmentsCategory::class,
                      ];
                      privatestatic$belongs_many_many = [
                      'Views' => View::class,
                      'MetricGroups' => MetricGroup::class,
                      ];
                      privatestatic$searchable_fields = [
                      'Name',
                      'Description',
                      ];
                      privatestatic$summary_fields = [
                      'Name' => 'Name',
                      'Description' => 'Description',
                      'IsAdminOnly' => 'Admin Only',
                      ];
                      privatestatic$casting = [
                      'IsAdminOnly' => 'Text',
                      ];
                      privatefunctionboolToText($input) {
                      if($input) {
                      return'yes';
                      } else {
                      return'no';
                      }
                      }
                      publicfunctiongetIsAdminOnly() {
                      return$this->boolToText($this->AdminOnly);
                      }
                      publicfunctionAttachmentsCategories() {
                      return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
                      }
                      publicfunctiongetCMSfields() {
                      $fields = FieldList::create(TabSet::create('Root'));
                      $fields->addFieldsToTab('Root.Main', [
                      CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                      TextField::create('Name'),
                      TextField::create('Description'),
                      ]);
                      $fields->addFieldsToTab('Root.Main', [
                      GridField::create(
                      'AttachmentsCategories',
                      'Attachments Categories',
                      $this->AttachmentsCategories(),
                      $gridConfig = SortableGridFieldConfig::create(),
                      ),
                      ]);
                      return$fields;
                      }
                      }
                      class AttachmentsCategory extends DataObject {
                      privatestatic$db = [
                      'Name' => 'Text',
                      'SortOrder' => 'Int',
                      ];
                      privatestatic$has_one = [
                      'Button' => Button::class,
                      ];
                      privatestatic$many_many = [
                      'Attachments' => Attachment::class,
                      ];
                      privatestatic$many_many_extraFields = [
                      'Attachments' => [
                      'SortOrder' => 'Int',
                      ],
                      ];
                      publicfunctionAttachments() {
                      return$this->getManyManyComponents('Attachments')->sort('SortOrder');
                      }
                      publicfunctiongetCMSfields() {
                      $fields = FieldList::create(TabSet::create('Root'));
                      $fields->addFieldsToTab('Root.Main', [
                      TextField::create('Name'),
                      ]);
                      $fields->addFieldsToTab('Root.Main', [
                      GridField::create(
                      'Attachments',
                      'Attachments',
                      $this->Attachments(),
                      SortableGridFieldConfig::create(),
                      ),
                      ]);
                      return$fields;
                      }
                      }
                      class Attachment extends DataObject {
                      privatestatic$db = [
                      'Name' => 'Text',
                      ];
                      privatestatic$has_one = [
                      'File' => File::class,
                      ];
                      privatestatic$belongs_many_many = [
                      'Category' => AttachmentsCategory::class,
                      ];
                      publicfunctiongetCMSfields() {
                      $fields = FieldList::create(TabSet::create('Root'));
                      $fields->addFieldsToTab('Root.Main', [
                      TextField::create('Name'),
                      UploadField::create('File'),
                      ]);
                      return$fields;
                      }
                      }

                      MetricGroup.php

                      <?phpuseSilverstripe\Forms\CheckboxField;
                      useSilverstripe\Forms\FieldList;
                      useSilverStripe\Forms\GridField\GridField;
                      useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                      useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                      useSilverStripe\Forms\TabSet;
                      useSilverstripe\Forms\TextField;
                      useSilverstripe\ORM\DataObject;
                      useSilverStripe\Security\Security;
                      class MetricGroup extends DataObject {
                      privatestatic$db = [
                      'AdminOnly' => 'Boolean(0)',
                      'Description' => 'HTMLText',
                      'Enabled' => 'Boolean(1)',
                      'Subtitle' => 'Text',
                      'Title' => 'Text',
                      ];
                      privatestatic$defaults = [
                      'Enabled' => true,
                      ];
                      privatestatic$belongs_many_many = [
                      'View' => View::class,
                      ];
                      privatestatic$many_many = [
                      'Buttons' => Button::class,
                      'Metrics' => Metric::class,
                      ];
                      privatestatic$many_many_extraFields = [
                      'Metrics' => [
                      'SortOrder' => 'Int',
                      ],
                      'Buttons' => [
                      'SortOrder' => 'Int',
                      ],
                      ];
                      privatestatic$summary_fields = [
                      'Title' => 'Title',
                      'Subtitle' => 'Subtitle',
                      'IsEnabled' => 'Enabled',
                      'IsAdminOnly' => 'Admin Only',
                      'MetricsCount' => 'Metrics',
                      ];
                      privatestatic$casting = [
                      'IsEnabled' => 'Text',
                      'IsAdminOnly' => 'Text',
                      'MetricsCount' => 'Text',
                      ];
                      privatefunctionboolToText($input) {
                      if($input) {
                      return'yes';
                      } else {
                      return'no';
                      }
                      }
                      publicfunctiongetIsEnabled() {
                      return$this->boolToText($this->Enabled);
                      }
                      publicfunctiongetIsAdminOnly() {
                      return$this->boolToText($this->AdminOnly);
                      }
                      publicfunctiongetMetricsCount() {
                      returncount($this->Metrics());
                      }
                      publicfunctionMetrics() {
                      $member = Security::getCurrentUser();
                      $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
                      if (!$member->inGroup('administrators')) {
                      $metrics = $metrics->filter(['AdminOnly' => false]);
                      }
                      return$metrics;
                      }
                      publicfunctionButtons() {
                      $member = Security::getCurrentUser();
                      $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                      if (!$member->inGroup('administrators')) {
                      $buttons = $buttons->filter(['AdminOnly' => false]);
                      }
                      return$buttons;
                      }
                      publicfunctiongetCMSfields() {
                      $fields = FieldList::create(TabSet::create('Root'));
                      $fields->addFieldsToTab('Root.Main', [
                      CheckboxField::create('Enabled'),
                      CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                      TextField::create('Title'),
                      TextField::create('Subtitle'),
                      HTMLEditorField::create('Description'),
                      GridField::create(
                      'Metrics',
                      'Metrics',
                      $this->Metrics(),
                      SortableGridFieldConfig::create()
                      ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
                      ),
                      ]);
                      $fields->addFieldsToTab('Root.Buttons', [
                      GridField::create(
                      'AttachmentsCategories',
                      'Attachments Categories',
                      $this->Buttons(),
                      $gridConfig = SortableGridFieldConfig::create(),
                      ),
                      ]);
                      $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                      return$fields;
                      }
                      }

                      View.php

                      <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                      useSilverStripe\Assets\File;
                      useSilverstripe\Forms\CheckboxField;
                      useSilverstripe\Forms\FieldList;
                      useSilverStripe\Forms\GridField\GridField;
                      useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                      useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                      useSilverStripe\Forms\TabSet;
                      useSilverstripe\Forms\TextField;
                      useSilverstripe\ORM\DataObject;
                      useSilverStripe\Security\Member;
                      useSilverStripe\View\Parsers\ShortcodeParser;
                      useSilverStripe\Security\Security;
                      class View extends DataObject {
                      privatestatic$db = [
                      'Enabled' => 'Boolean(1)',
                      'Name' => 'Text',
                      'Code' => 'Text',
                      'Factory' => 'Text',
                      'Description' => 'HTMLText',
                      ];
                      privatestatic$defaults = [
                      'Enabled' => true,
                      ];
                      privatestatic$has_one = [
                      'Picture' => File::class,
                      'Graphic' => File::class,
                      ];
                      privatestatic$belongs_many_many = [
                      'Owners' => Member::class,
                      ];
                      privatestatic$many_many = [
                      'Buttons' => Button::class,
                      'MetricGroups' => MetricGroup::class,
                      ];
                      privatestatic$many_many_extraFields = [
                      'MetricGroups' => [
                      'SortOrder' => 'Int',
                      ],
                      'Buttons' => [
                      'SortOrder' => 'Int',
                      ],
                      ];
                      privatestatic$summary_fields = [
                      'Name' => 'Name',
                      'Code' => 'Code',
                      'IsEnabled' => 'Enabled',
                      ];
                      privatestatic$casting = [
                      'IsEnabled' => 'Text',
                      ];
                      privatefunctionboolToText($input) {
                      if($input) {
                      return'yes';
                      } else {
                      return'no';
                      }
                      }
                      publicfunctiongetIsEnabled() {
                      return$this->boolToText($this->Enabled);
                      }
                      publicfunctionMetricGroups() {
                      $member = Security::getCurrentUser();
                      $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
                      if (!$member->inGroup('administrators')) {
                      $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
                      }
                      return$metric_groups;
                      }
                      publicfunctionButtons() {
                      $member = Security::getCurrentUser();
                      $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                      if (!$member->inGroup('administrators')) {
                      $buttons = $buttons->filter(['AdminOnly' => false]);
                      }
                      return$buttons;
                      }
                      publicfunctiongetCMSfields() {
                      $fields = FieldList::create(TabSet::create('Root'));
                      $fields->addFieldsToTab('Root.Main', [
                      CheckboxField::create('Enabled'),
                      TextField::create('Name'),
                      TextField::create('Code'),
                      TextField::create('Factory'),
                      HTMLEditorField::create('Description'),
                      UploadField::create('Graphic', 'Graphic for main menu'),
                      UploadField::create('Picture', 'Synoptic'),
                      ]);
                      $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
                      'MetricGroups',
                      'Metric Groups',
                      $this->MetricGroups(),
                      SortableGridFieldConfig::create(),
                      ),
                      ]);
                      $fields->addFieldsToTab('Root.Buttons', [
                      GridField::create(
                      'AttachmentsCategories',
                      'Attachments Categories',
                      $this->Buttons(),
                      $gridConfig = SortableGridFieldConfig::create(),
                      ),
                      ]);
                      $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                      return$fields;
                      }
                      }

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Creating new DataObject results in database query with empty table and column names #139

                          Description

                          @leonixyz

                          Affected Version

                          {
                          "php": "^8.1",
                          "silverstripe/recipe-plugin": "~2.0.0@stable",
                          "silverstripe/vendor-plugin": "~2.0.0@stable",
                          "silverstripe/recipe-cms": "~5.0.0@stable",
                          "silverstripe/login-forms": "~5.0.0@stable",
                          "silverstripe/display-logic": "^3.0",
                          "undefinedoffset/sortablegridfield": "^2.2"
                          }

                          Description

                          A wrong query is issued to the database, having empty table name and empty column names.

                          UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
                          

                          If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

                          Peek.2024-05-11.23-32.mp4

                          Stack trace:

                          [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
                          GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
                          Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
                          Source
                          55 if (!empty($sql)) {
                          56 $formatter = new SQLFormatter();
                          57 $formattedSQL = $formatter->formatPlain($sql);
                          58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
                          59 }
                          60 61 if ($errorLevel === E_USER_ERROR) {
                          62 // Treating errors as exceptions better allows for responding to errors
                          63 // in code, such as credential checking during installation
                          64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
                          65 } else {
                          66 user_error($msg ?? '', $errorLevel ?? 0);
                          67 }
                          68 }
                          69 70 /**
                          Trace
                          SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                          MySQLiConnector.php:194
                          SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                          Database.php:159
                          SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                          Database.php:258
                          SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
                          Database.php:160
                          SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                          MySQLDatabase.php:381
                          SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                          DB.php:341
                          SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                          GridFieldSortableRows.php:329
                          UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                          GridFieldSortableRows.php:151
                          UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                          GridField.php:411
                          SilverStripe\Forms\GridField\GridField->getManipulatedList()
                          GridFieldDetailForm_ItemRequest.php:573
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
                          GridFieldDetailForm_ItemRequest.php:654
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
                          GridFieldDetailForm_ItemRequest.php:682
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
                          GridFieldDetailForm_ItemRequest.php:334
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
                          GridFieldDetailForm_ItemRequest.php:420
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
                          VersionedGridFieldItemRequest.php:90
                          SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
                          GridFieldDetailForm_ItemRequest.php:242
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
                          GridFieldDetailForm_ItemRequest.php:160
                          SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
                          RequestHandler.php:323
                          SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
                          RequestHandler.php:202
                          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                          GridFieldDetailForm.php:149
                          SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                          GridField.php:1237
                          SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                          RequestHandler.php:226
                          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                          RequestHandler.php:226
                          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                          GridFieldDetailForm.php:149
                          SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                          GridField.php:1237
                          SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                          RequestHandler.php:226
                          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                          RequestHandler.php:226
                          SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                          Controller.php:202
                          SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
                          LeftAndMain.php:799
                          SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
                          AdminRootController.php:124
                          SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
                          Director.php:349
                          SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                          VersionedHTTPMiddleware.php:41
                          SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          LoginSessionMiddleware.php:53
                          SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          ExecMetricMiddleware.php:20
                          SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          ConfirmationMiddleware.php:254
                          SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          ConfirmationMiddleware.php:254
                          SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          PasswordExpirationMiddleware.php:84
                          SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          BasicAuthMiddleware.php:68
                          SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          AuthenticationMiddleware.php:61
                          SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          CanonicalURLMiddleware.php:245
                          SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          HTTPCacheControlMiddleware.php:41
                          SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          ChangeDetectionMiddleware.php:28
                          SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          FlushMiddleware.php:31
                          SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          SessionMiddleware.php:20
                          SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          AllowedHostsMiddleware.php:60
                          SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          TrustedProxyMiddleware.php:176
                          SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPMiddlewareAware.php:62
                          SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                          HTTPMiddlewareAware.php:65
                          SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                          Director.php:358
                          SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
                          HTTPApplication.php:114
                          SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                          call_user_func(Closure, SilverStripe\Control\HTTPRequest)
                          HTTPApplication.php:137
                          SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                          HTTPMiddlewareAware.php:65
                          SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                          HTTPApplication.php:138
                          SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
                          HTTPApplication.php:113
                          SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
                          index.php:24
                          

                          Steps to Reproduce

                          Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

                          ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
                          └─────────────┘ │ │ │ │ │ │
                          │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
                          ┌──────────────┐ │ │1 *│ │* *│ │
                          │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
                          │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

                          Please forgive me if the code is redundant.

                          GridFieldConfig.php

                          <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
                          useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
                          class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
                          publicstaticfunctioncreate(mixed ...$args) {
                          $config = GridFieldConfig_RelationEditor::create(50);
                          $config->addComponent(GridFieldSortableRows::create('SortOrder'));
                          return$config;
                          }
                          }

                          Attachments.php

                          <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                          useSilverStripe\Assets\File;
                          useSilverstripe\Forms\CheckboxField;
                          useSilverstripe\Forms\FieldList;
                          useSilverStripe\Forms\GridField\GridField;
                          useSilverStripe\Forms\TabSet;
                          useSilverstripe\Forms\TextField;
                          useSilverstripe\ORM\DataObject;
                          class Button extends DataObject {
                          privatestatic$db = [
                          'AdminOnly' => 'Boolean(0)',
                          'Description' => 'Text',
                          'Name' => 'Text',
                          ];
                          privatestatic$has_many = [
                          'AttachmentsCategories' => AttachmentsCategory::class,
                          ];
                          privatestatic$belongs_many_many = [
                          'Views' => View::class,
                          'MetricGroups' => MetricGroup::class,
                          ];
                          privatestatic$searchable_fields = [
                          'Name',
                          'Description',
                          ];
                          privatestatic$summary_fields = [
                          'Name' => 'Name',
                          'Description' => 'Description',
                          'IsAdminOnly' => 'Admin Only',
                          ];
                          privatestatic$casting = [
                          'IsAdminOnly' => 'Text',
                          ];
                          privatefunctionboolToText($input) {
                          if($input) {
                          return'yes';
                          } else {
                          return'no';
                          }
                          }
                          publicfunctiongetIsAdminOnly() {
                          return$this->boolToText($this->AdminOnly);
                          }
                          publicfunctionAttachmentsCategories() {
                          return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
                          }
                          publicfunctiongetCMSfields() {
                          $fields = FieldList::create(TabSet::create('Root'));
                          $fields->addFieldsToTab('Root.Main', [
                          CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                          TextField::create('Name'),
                          TextField::create('Description'),
                          ]);
                          $fields->addFieldsToTab('Root.Main', [
                          GridField::create(
                          'AttachmentsCategories',
                          'Attachments Categories',
                          $this->AttachmentsCategories(),
                          $gridConfig = SortableGridFieldConfig::create(),
                          ),
                          ]);
                          return$fields;
                          }
                          }
                          class AttachmentsCategory extends DataObject {
                          privatestatic$db = [
                          'Name' => 'Text',
                          'SortOrder' => 'Int',
                          ];
                          privatestatic$has_one = [
                          'Button' => Button::class,
                          ];
                          privatestatic$many_many = [
                          'Attachments' => Attachment::class,
                          ];
                          privatestatic$many_many_extraFields = [
                          'Attachments' => [
                          'SortOrder' => 'Int',
                          ],
                          ];
                          publicfunctionAttachments() {
                          return$this->getManyManyComponents('Attachments')->sort('SortOrder');
                          }
                          publicfunctiongetCMSfields() {
                          $fields = FieldList::create(TabSet::create('Root'));
                          $fields->addFieldsToTab('Root.Main', [
                          TextField::create('Name'),
                          ]);
                          $fields->addFieldsToTab('Root.Main', [
                          GridField::create(
                          'Attachments',
                          'Attachments',
                          $this->Attachments(),
                          SortableGridFieldConfig::create(),
                          ),
                          ]);
                          return$fields;
                          }
                          }
                          class Attachment extends DataObject {
                          privatestatic$db = [
                          'Name' => 'Text',
                          ];
                          privatestatic$has_one = [
                          'File' => File::class,
                          ];
                          privatestatic$belongs_many_many = [
                          'Category' => AttachmentsCategory::class,
                          ];
                          publicfunctiongetCMSfields() {
                          $fields = FieldList::create(TabSet::create('Root'));
                          $fields->addFieldsToTab('Root.Main', [
                          TextField::create('Name'),
                          UploadField::create('File'),
                          ]);
                          return$fields;
                          }
                          }

                          MetricGroup.php

                          <?phpuseSilverstripe\Forms\CheckboxField;
                          useSilverstripe\Forms\FieldList;
                          useSilverStripe\Forms\GridField\GridField;
                          useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                          useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                          useSilverStripe\Forms\TabSet;
                          useSilverstripe\Forms\TextField;
                          useSilverstripe\ORM\DataObject;
                          useSilverStripe\Security\Security;
                          class MetricGroup extends DataObject {
                          privatestatic$db = [
                          'AdminOnly' => 'Boolean(0)',
                          'Description' => 'HTMLText',
                          'Enabled' => 'Boolean(1)',
                          'Subtitle' => 'Text',
                          'Title' => 'Text',
                          ];
                          privatestatic$defaults = [
                          'Enabled' => true,
                          ];
                          privatestatic$belongs_many_many = [
                          'View' => View::class,
                          ];
                          privatestatic$many_many = [
                          'Buttons' => Button::class,
                          'Metrics' => Metric::class,
                          ];
                          privatestatic$many_many_extraFields = [
                          'Metrics' => [
                          'SortOrder' => 'Int',
                          ],
                          'Buttons' => [
                          'SortOrder' => 'Int',
                          ],
                          ];
                          privatestatic$summary_fields = [
                          'Title' => 'Title',
                          'Subtitle' => 'Subtitle',
                          'IsEnabled' => 'Enabled',
                          'IsAdminOnly' => 'Admin Only',
                          'MetricsCount' => 'Metrics',
                          ];
                          privatestatic$casting = [
                          'IsEnabled' => 'Text',
                          'IsAdminOnly' => 'Text',
                          'MetricsCount' => 'Text',
                          ];
                          privatefunctionboolToText($input) {
                          if($input) {
                          return'yes';
                          } else {
                          return'no';
                          }
                          }
                          publicfunctiongetIsEnabled() {
                          return$this->boolToText($this->Enabled);
                          }
                          publicfunctiongetIsAdminOnly() {
                          return$this->boolToText($this->AdminOnly);
                          }
                          publicfunctiongetMetricsCount() {
                          returncount($this->Metrics());
                          }
                          publicfunctionMetrics() {
                          $member = Security::getCurrentUser();
                          $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
                          if (!$member->inGroup('administrators')) {
                          $metrics = $metrics->filter(['AdminOnly' => false]);
                          }
                          return$metrics;
                          }
                          publicfunctionButtons() {
                          $member = Security::getCurrentUser();
                          $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                          if (!$member->inGroup('administrators')) {
                          $buttons = $buttons->filter(['AdminOnly' => false]);
                          }
                          return$buttons;
                          }
                          publicfunctiongetCMSfields() {
                          $fields = FieldList::create(TabSet::create('Root'));
                          $fields->addFieldsToTab('Root.Main', [
                          CheckboxField::create('Enabled'),
                          CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                          TextField::create('Title'),
                          TextField::create('Subtitle'),
                          HTMLEditorField::create('Description'),
                          GridField::create(
                          'Metrics',
                          'Metrics',
                          $this->Metrics(),
                          SortableGridFieldConfig::create()
                          ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
                          ),
                          ]);
                          $fields->addFieldsToTab('Root.Buttons', [
                          GridField::create(
                          'AttachmentsCategories',
                          'Attachments Categories',
                          $this->Buttons(),
                          $gridConfig = SortableGridFieldConfig::create(),
                          ),
                          ]);
                          $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                          return$fields;
                          }
                          }

                          View.php

                          <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                          useSilverStripe\Assets\File;
                          useSilverstripe\Forms\CheckboxField;
                          useSilverstripe\Forms\FieldList;
                          useSilverStripe\Forms\GridField\GridField;
                          useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                          useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                          useSilverStripe\Forms\TabSet;
                          useSilverstripe\Forms\TextField;
                          useSilverstripe\ORM\DataObject;
                          useSilverStripe\Security\Member;
                          useSilverStripe\View\Parsers\ShortcodeParser;
                          useSilverStripe\Security\Security;
                          class View extends DataObject {
                          privatestatic$db = [
                          'Enabled' => 'Boolean(1)',
                          'Name' => 'Text',
                          'Code' => 'Text',
                          'Factory' => 'Text',
                          'Description' => 'HTMLText',
                          ];
                          privatestatic$defaults = [
                          'Enabled' => true,
                          ];
                          privatestatic$has_one = [
                          'Picture' => File::class,
                          'Graphic' => File::class,
                          ];
                          privatestatic$belongs_many_many = [
                          'Owners' => Member::class,
                          ];
                          privatestatic$many_many = [
                          'Buttons' => Button::class,
                          'MetricGroups' => MetricGroup::class,
                          ];
                          privatestatic$many_many_extraFields = [
                          'MetricGroups' => [
                          'SortOrder' => 'Int',
                          ],
                          'Buttons' => [
                          'SortOrder' => 'Int',
                          ],
                          ];
                          privatestatic$summary_fields = [
                          'Name' => 'Name',
                          'Code' => 'Code',
                          'IsEnabled' => 'Enabled',
                          ];
                          privatestatic$casting = [
                          'IsEnabled' => 'Text',
                          ];
                          privatefunctionboolToText($input) {
                          if($input) {
                          return'yes';
                          } else {
                          return'no';
                          }
                          }
                          publicfunctiongetIsEnabled() {
                          return$this->boolToText($this->Enabled);
                          }
                          publicfunctionMetricGroups() {
                          $member = Security::getCurrentUser();
                          $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
                          if (!$member->inGroup('administrators')) {
                          $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
                          }
                          return$metric_groups;
                          }
                          publicfunctionButtons() {
                          $member = Security::getCurrentUser();
                          $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                          if (!$member->inGroup('administrators')) {
                          $buttons = $buttons->filter(['AdminOnly' => false]);
                          }
                          return$buttons;
                          }
                          publicfunctiongetCMSfields() {
                          $fields = FieldList::create(TabSet::create('Root'));
                          $fields->addFieldsToTab('Root.Main', [
                          CheckboxField::create('Enabled'),
                          TextField::create('Name'),
                          TextField::create('Code'),
                          TextField::create('Factory'),
                          HTMLEditorField::create('Description'),
                          UploadField::create('Graphic', 'Graphic for main menu'),
                          UploadField::create('Picture', 'Synoptic'),
                          ]);
                          $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
                          'MetricGroups',
                          'Metric Groups',
                          $this->MetricGroups(),
                          SortableGridFieldConfig::create(),
                          ),
                          ]);
                          $fields->addFieldsToTab('Root.Buttons', [
                          GridField::create(
                          'AttachmentsCategories',
                          'Attachments Categories',
                          $this->Buttons(),
                          $gridConfig = SortableGridFieldConfig::create(),
                          ),
                          ]);
                          $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                          return$fields;
                          }
                          }

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Creating new DataObject results in database query with empty table and column names #139

                              Description

                              @leonixyz

                              Affected Version

                              {
                              "php": "^8.1",
                              "silverstripe/recipe-plugin": "~2.0.0@stable",
                              "silverstripe/vendor-plugin": "~2.0.0@stable",
                              "silverstripe/recipe-cms": "~5.0.0@stable",
                              "silverstripe/login-forms": "~5.0.0@stable",
                              "silverstripe/display-logic": "^3.0",
                              "undefinedoffset/sortablegridfield": "^2.2"
                              }

                              Description

                              A wrong query is issued to the database, having empty table name and empty column names.

                              UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
                              

                              If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

                              Peek.2024-05-11.23-32.mp4

                              Stack trace:

                              [Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
                              GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
                              Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
                              Source
                              55 if (!empty($sql)) {
                              56 $formatter = new SQLFormatter();
                              57 $formattedSQL = $formatter->formatPlain($sql);
                              58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
                              59 }
                              60 61 if ($errorLevel === E_USER_ERROR) {
                              62 // Treating errors as exceptions better allows for responding to errors
                              63 // in code, such as credential checking during installation
                              64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
                              65 } else {
                              66 user_error($msg ?? '', $errorLevel ?? 0);
                              67 }
                              68 }
                              69 70 /**
                              Trace
                              SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                              MySQLiConnector.php:194
                              SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                              Database.php:159
                              SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                              Database.php:258
                              SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
                              Database.php:160
                              SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                              MySQLDatabase.php:381
                              SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
                              DB.php:341
                              SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
                              GridFieldSortableRows.php:329
                              UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                              GridFieldSortableRows.php:151
                              UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
                              GridField.php:411
                              SilverStripe\Forms\GridField\GridField->getManipulatedList()
                              GridFieldDetailForm_ItemRequest.php:573
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
                              GridFieldDetailForm_ItemRequest.php:654
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
                              GridFieldDetailForm_ItemRequest.php:682
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
                              GridFieldDetailForm_ItemRequest.php:334
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
                              GridFieldDetailForm_ItemRequest.php:420
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
                              VersionedGridFieldItemRequest.php:90
                              SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
                              GridFieldDetailForm_ItemRequest.php:242
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
                              GridFieldDetailForm_ItemRequest.php:160
                              SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
                              RequestHandler.php:323
                              SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
                              RequestHandler.php:202
                              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                              GridFieldDetailForm.php:149
                              SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                              GridField.php:1237
                              SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                              RequestHandler.php:226
                              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                              RequestHandler.php:226
                              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                              GridFieldDetailForm.php:149
                              SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
                              GridField.php:1237
                              SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
                              RequestHandler.php:226
                              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                              RequestHandler.php:226
                              SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
                              Controller.php:202
                              SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
                              LeftAndMain.php:799
                              SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
                              AdminRootController.php:124
                              SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
                              Director.php:349
                              SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                              VersionedHTTPMiddleware.php:41
                              SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              LoginSessionMiddleware.php:53
                              SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              ExecMetricMiddleware.php:20
                              SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              ConfirmationMiddleware.php:254
                              SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              ConfirmationMiddleware.php:254
                              SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              PasswordExpirationMiddleware.php:84
                              SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              BasicAuthMiddleware.php:68
                              SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              AuthenticationMiddleware.php:61
                              SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              CanonicalURLMiddleware.php:245
                              SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              HTTPCacheControlMiddleware.php:41
                              SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              ChangeDetectionMiddleware.php:28
                              SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              FlushMiddleware.php:31
                              SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              SessionMiddleware.php:20
                              SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              AllowedHostsMiddleware.php:60
                              SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              TrustedProxyMiddleware.php:176
                              SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPMiddlewareAware.php:62
                              SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
                              HTTPMiddlewareAware.php:65
                              SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                              Director.php:358
                              SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
                              HTTPApplication.php:114
                              SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                              call_user_func(Closure, SilverStripe\Control\HTTPRequest)
                              HTTPApplication.php:137
                              SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
                              HTTPMiddlewareAware.php:65
                              SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
                              HTTPApplication.php:138
                              SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
                              HTTPApplication.php:113
                              SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
                              index.php:24
                              

                              Steps to Reproduce

                              Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

                              ┌─────────────┐ │ │ │ View ├───────┐* │ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
                              └─────────────┘ │ │ │ │ │ │
                              │ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
                              ┌──────────────┐ │ │1 *│ │* *│ │
                              │ │ ├─────────┘ └─────────────────────┘ └───────────────┘
                              │ MetricGroup │ │* │ ├──────┘ └──────────────┘* 

                              Please forgive me if the code is redundant.

                              GridFieldConfig.php

                              <?phpuseUndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
                              useSilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
                              class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
                              publicstaticfunctioncreate(mixed ...$args) {
                              $config = GridFieldConfig_RelationEditor::create(50);
                              $config->addComponent(GridFieldSortableRows::create('SortOrder'));
                              return$config;
                              }
                              }

                              Attachments.php

                              <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                              useSilverStripe\Assets\File;
                              useSilverstripe\Forms\CheckboxField;
                              useSilverstripe\Forms\FieldList;
                              useSilverStripe\Forms\GridField\GridField;
                              useSilverStripe\Forms\TabSet;
                              useSilverstripe\Forms\TextField;
                              useSilverstripe\ORM\DataObject;
                              class Button extends DataObject {
                              privatestatic$db = [
                              'AdminOnly' => 'Boolean(0)',
                              'Description' => 'Text',
                              'Name' => 'Text',
                              ];
                              privatestatic$has_many = [
                              'AttachmentsCategories' => AttachmentsCategory::class,
                              ];
                              privatestatic$belongs_many_many = [
                              'Views' => View::class,
                              'MetricGroups' => MetricGroup::class,
                              ];
                              privatestatic$searchable_fields = [
                              'Name',
                              'Description',
                              ];
                              privatestatic$summary_fields = [
                              'Name' => 'Name',
                              'Description' => 'Description',
                              'IsAdminOnly' => 'Admin Only',
                              ];
                              privatestatic$casting = [
                              'IsAdminOnly' => 'Text',
                              ];
                              privatefunctionboolToText($input) {
                              if($input) {
                              return'yes';
                              } else {
                              return'no';
                              }
                              }
                              publicfunctiongetIsAdminOnly() {
                              return$this->boolToText($this->AdminOnly);
                              }
                              publicfunctionAttachmentsCategories() {
                              return$this->getComponents('AttachmentsCategories')->sort('SortOrder');
                              }
                              publicfunctiongetCMSfields() {
                              $fields = FieldList::create(TabSet::create('Root'));
                              $fields->addFieldsToTab('Root.Main', [
                              CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                              TextField::create('Name'),
                              TextField::create('Description'),
                              ]);
                              $fields->addFieldsToTab('Root.Main', [
                              GridField::create(
                              'AttachmentsCategories',
                              'Attachments Categories',
                              $this->AttachmentsCategories(),
                              $gridConfig = SortableGridFieldConfig::create(),
                              ),
                              ]);
                              return$fields;
                              }
                              }
                              class AttachmentsCategory extends DataObject {
                              privatestatic$db = [
                              'Name' => 'Text',
                              'SortOrder' => 'Int',
                              ];
                              privatestatic$has_one = [
                              'Button' => Button::class,
                              ];
                              privatestatic$many_many = [
                              'Attachments' => Attachment::class,
                              ];
                              privatestatic$many_many_extraFields = [
                              'Attachments' => [
                              'SortOrder' => 'Int',
                              ],
                              ];
                              publicfunctionAttachments() {
                              return$this->getManyManyComponents('Attachments')->sort('SortOrder');
                              }
                              publicfunctiongetCMSfields() {
                              $fields = FieldList::create(TabSet::create('Root'));
                              $fields->addFieldsToTab('Root.Main', [
                              TextField::create('Name'),
                              ]);
                              $fields->addFieldsToTab('Root.Main', [
                              GridField::create(
                              'Attachments',
                              'Attachments',
                              $this->Attachments(),
                              SortableGridFieldConfig::create(),
                              ),
                              ]);
                              return$fields;
                              }
                              }
                              class Attachment extends DataObject {
                              privatestatic$db = [
                              'Name' => 'Text',
                              ];
                              privatestatic$has_one = [
                              'File' => File::class,
                              ];
                              privatestatic$belongs_many_many = [
                              'Category' => AttachmentsCategory::class,
                              ];
                              publicfunctiongetCMSfields() {
                              $fields = FieldList::create(TabSet::create('Root'));
                              $fields->addFieldsToTab('Root.Main', [
                              TextField::create('Name'),
                              UploadField::create('File'),
                              ]);
                              return$fields;
                              }
                              }

                              MetricGroup.php

                              <?phpuseSilverstripe\Forms\CheckboxField;
                              useSilverstripe\Forms\FieldList;
                              useSilverStripe\Forms\GridField\GridField;
                              useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                              useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                              useSilverStripe\Forms\TabSet;
                              useSilverstripe\Forms\TextField;
                              useSilverstripe\ORM\DataObject;
                              useSilverStripe\Security\Security;
                              class MetricGroup extends DataObject {
                              privatestatic$db = [
                              'AdminOnly' => 'Boolean(0)',
                              'Description' => 'HTMLText',
                              'Enabled' => 'Boolean(1)',
                              'Subtitle' => 'Text',
                              'Title' => 'Text',
                              ];
                              privatestatic$defaults = [
                              'Enabled' => true,
                              ];
                              privatestatic$belongs_many_many = [
                              'View' => View::class,
                              ];
                              privatestatic$many_many = [
                              'Buttons' => Button::class,
                              'Metrics' => Metric::class,
                              ];
                              privatestatic$many_many_extraFields = [
                              'Metrics' => [
                              'SortOrder' => 'Int',
                              ],
                              'Buttons' => [
                              'SortOrder' => 'Int',
                              ],
                              ];
                              privatestatic$summary_fields = [
                              'Title' => 'Title',
                              'Subtitle' => 'Subtitle',
                              'IsEnabled' => 'Enabled',
                              'IsAdminOnly' => 'Admin Only',
                              'MetricsCount' => 'Metrics',
                              ];
                              privatestatic$casting = [
                              'IsEnabled' => 'Text',
                              'IsAdminOnly' => 'Text',
                              'MetricsCount' => 'Text',
                              ];
                              privatefunctionboolToText($input) {
                              if($input) {
                              return'yes';
                              } else {
                              return'no';
                              }
                              }
                              publicfunctiongetIsEnabled() {
                              return$this->boolToText($this->Enabled);
                              }
                              publicfunctiongetIsAdminOnly() {
                              return$this->boolToText($this->AdminOnly);
                              }
                              publicfunctiongetMetricsCount() {
                              returncount($this->Metrics());
                              }
                              publicfunctionMetrics() {
                              $member = Security::getCurrentUser();
                              $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
                              if (!$member->inGroup('administrators')) {
                              $metrics = $metrics->filter(['AdminOnly' => false]);
                              }
                              return$metrics;
                              }
                              publicfunctionButtons() {
                              $member = Security::getCurrentUser();
                              $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                              if (!$member->inGroup('administrators')) {
                              $buttons = $buttons->filter(['AdminOnly' => false]);
                              }
                              return$buttons;
                              }
                              publicfunctiongetCMSfields() {
                              $fields = FieldList::create(TabSet::create('Root'));
                              $fields->addFieldsToTab('Root.Main', [
                              CheckboxField::create('Enabled'),
                              CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
                              TextField::create('Title'),
                              TextField::create('Subtitle'),
                              HTMLEditorField::create('Description'),
                              GridField::create(
                              'Metrics',
                              'Metrics',
                              $this->Metrics(),
                              SortableGridFieldConfig::create()
                              ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
                              ),
                              ]);
                              $fields->addFieldsToTab('Root.Buttons', [
                              GridField::create(
                              'AttachmentsCategories',
                              'Attachments Categories',
                              $this->Buttons(),
                              $gridConfig = SortableGridFieldConfig::create(),
                              ),
                              ]);
                              $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                              return$fields;
                              }
                              }

                              View.php

                              <?phpuseSilverStripe\AssetAdmin\Forms\UploadField;
                              useSilverStripe\Assets\File;
                              useSilverstripe\Forms\CheckboxField;
                              useSilverstripe\Forms\FieldList;
                              useSilverStripe\Forms\GridField\GridField;
                              useSilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
                              useSilverstripe\Forms\HTMLEditor\HTMLEditorField;
                              useSilverStripe\Forms\TabSet;
                              useSilverstripe\Forms\TextField;
                              useSilverstripe\ORM\DataObject;
                              useSilverStripe\Security\Member;
                              useSilverStripe\View\Parsers\ShortcodeParser;
                              useSilverStripe\Security\Security;
                              class View extends DataObject {
                              privatestatic$db = [
                              'Enabled' => 'Boolean(1)',
                              'Name' => 'Text',
                              'Code' => 'Text',
                              'Factory' => 'Text',
                              'Description' => 'HTMLText',
                              ];
                              privatestatic$defaults = [
                              'Enabled' => true,
                              ];
                              privatestatic$has_one = [
                              'Picture' => File::class,
                              'Graphic' => File::class,
                              ];
                              privatestatic$belongs_many_many = [
                              'Owners' => Member::class,
                              ];
                              privatestatic$many_many = [
                              'Buttons' => Button::class,
                              'MetricGroups' => MetricGroup::class,
                              ];
                              privatestatic$many_many_extraFields = [
                              'MetricGroups' => [
                              'SortOrder' => 'Int',
                              ],
                              'Buttons' => [
                              'SortOrder' => 'Int',
                              ],
                              ];
                              privatestatic$summary_fields = [
                              'Name' => 'Name',
                              'Code' => 'Code',
                              'IsEnabled' => 'Enabled',
                              ];
                              privatestatic$casting = [
                              'IsEnabled' => 'Text',
                              ];
                              privatefunctionboolToText($input) {
                              if($input) {
                              return'yes';
                              } else {
                              return'no';
                              }
                              }
                              publicfunctiongetIsEnabled() {
                              return$this->boolToText($this->Enabled);
                              }
                              publicfunctionMetricGroups() {
                              $member = Security::getCurrentUser();
                              $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
                              if (!$member->inGroup('administrators')) {
                              $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
                              }
                              return$metric_groups;
                              }
                              publicfunctionButtons() {
                              $member = Security::getCurrentUser();
                              $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
                              if (!$member->inGroup('administrators')) {
                              $buttons = $buttons->filter(['AdminOnly' => false]);
                              }
                              return$buttons;
                              }
                              publicfunctiongetCMSfields() {
                              $fields = FieldList::create(TabSet::create('Root'));
                              $fields->addFieldsToTab('Root.Main', [
                              CheckboxField::create('Enabled'),
                              TextField::create('Name'),
                              TextField::create('Code'),
                              TextField::create('Factory'),
                              HTMLEditorField::create('Description'),
                              UploadField::create('Graphic', 'Graphic for main menu'),
                              UploadField::create('Picture', 'Synoptic'),
                              ]);
                              $fields->addFieldsToTab('Root.MetricGroups', [ GridField::create(
                              'MetricGroups',
                              'Metric Groups',
                              $this->MetricGroups(),
                              SortableGridFieldConfig::create(),
                              ),
                              ]);
                              $fields->addFieldsToTab('Root.Buttons', [
                              GridField::create(
                              'AttachmentsCategories',
                              'Attachments Categories',
                              $this->Buttons(),
                              $gridConfig = SortableGridFieldConfig::create(),
                              ),
                              ]);
                              $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
                              return$fields;
                              }
                              }

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions