Cannot create new training

Created on 16 August 2023, over 1 year ago

Problem/Motivation

Recently updated from 3.0.9 => 3.1.0;
When I try to create a new training I receive the following error:

Drupal\Core\Database\IntegrityConstraintViolationException: SQLSTATE[HY000]: General error: 1364 Field 'revision_id' doesn't have a default value: INSERT INTO "groups_field_data" ("id", "type", "langcode", "uid", "label", "created", "changed", "default_langcode") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7); Array ( [:db_insert_placeholder_0] => 17 [:db_insert_placeholder_1] => learning_path [:db_insert_placeholder_2] => en [:db_insert_placeholder_3] => 2 [:db_insert_placeholder_4] => Software development training [:db_insert_placeholder_5] => 1692195455 [:db_insert_placeholder_6] => 1692195455 [:db_insert_placeholder_7] => 1 ) in Drupal\mysql\Driver\Database\mysql\ExceptionHandler->handleExecutionException() (line 50 of /code/web/core/modules/mysql/src/Driver/Database/mysql/ExceptionHandler.php).

Call stack

Drupal\Core\Database\StatementWrapper->execute(Array, Array) (Line: 44)
Drupal\mysql\Driver\Database\mysql\Insert->execute() (Line: 1020)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->saveToSharedTables(Object) (Line: 958)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->doSaveFieldItems(Object) (Line: 718)
Drupal\Core\Entity\ContentEntityStorageBase->doSave(NULL, Object) (Line: 520)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 804)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 339)
Drupal\Core\Entity\EntityBase->save() (Line: 285)
Drupal\Core\Entity\EntityForm->save(Array, Object) (Line: 88)
Drupal\group\Entity\Form\GroupForm->save(Array, Object)
call_user_func_array(Array, Array) (Line: 114)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 52)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 597)
Drupal\Core\Form\FormBuilder->processForm('group_learning_path_add_form', Array, Object) (Line: 325)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 48)
Drupal\Core\Entity\EntityFormBuilder->getForm(Object, 'add', Array) (Line: 139)

Drupal\group\Entity\Controller\GroupController->addForm(Object)

call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 169)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 718)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

In an attempt to track down where "revision_id" should be, I added some logs to the function addForm() seen in the call stack above

  public function addForm(GroupTypeInterface $group_type) {

    // Log the group type being processed.
    \Drupal::logger('addForm1')->notice('Starting addForm for group type: @group_type_id', ['@group_type_id' => $group_type->id()]);

This log returns: Starting addForm for group type: learning_path

    $wizard_id = 'group_creator';
    $store = $this->privateTempStoreFactory->get($wizard_id);
    $store_id = $group_type->id();

    // See if the group type is configured to ask the creator to fill out their
    // membership details. Also pass this info to the form state.
    $extra['group_wizard'] = $group_type->creatorMustCompleteMembership();
    $extra['group_wizard_id'] = $wizard_id;

    // Pass the group type and store ID to the form state as well.
    $extra['group_type'] = $group_type;
    $extra['store_id'] = $store_id;

    // See if we are on the second step of the form.
    $step2 = $extra['group_wizard'] && $store->get("$store_id:step") === 2;

    
    // Group form, potentially as wizard step 1.
    if (!$step2) {
      $storage = $this->entityTypeManager()->getStorage('group');
      
      // Only create a new group if we have nothing stored.
      if (!$entity = $store->get("$store_id:entity")) {
        $values['type'] = $group_type->id();
        
        // Log the values used to create the group or group membership entity.
        \Drupal::logger('addForm2')->notice('Creating entity with values: @values', ['@values' => print_r($values, TRUE)]); 

This log returns: Creating entity with values: Array ( [type] => learning_path )

        $entity = $storage->create($values);
      }

      // Log the group entity details.
      \Drupal::logger('addForm3')->notice('Group entity created: @entity', ['@entity' => print_r($entity->toArray(), TRUE)]); 

This log returns: Group entity created:
Array (
[id] => Array ( )
[uuid] => Array ( [0] => Array ( [value] => 53d30af7-b2c7-44d6-a310-a77bd85d3b1d ) )
[langcode] => Array ( [0] => Array ( [value] => en ) )
[type] => Array ( [0] => Array ( [target_id] => learning_path ) )
[uid] => Array ( [0] => Array ( [target_id] => 2 ) )
[label] => Array ( ) [created] => Array ( [0] => Array ( [value] => 1692195455 ) )
[changed] => Array ( [0] => Array ( [value] => 1692195455 ) )
[path] => Array ( [0] => Array ( [langcode] => en ) )
[default_langcode] => Array ( [0] => Array ( [value] => 1 ) )
[field_anonymous_visibility] => Array ( [0] => Array ( [value] => 0 ) )
[field_certificate] => Array ( )
[field_certificate_expire] => Array ( [0] => Array ( [value] => 0 ) )
[field_certificate_expire_results] => Array ( [0] => Array ( [value] => 1 ) )
[field_guided_navigation] => Array ( [0] => Array ( [value] => 0 ) )
[field_learning_path_category] => Array ( )
[field_learning_path_description] => Array ( )
[field_learning_path_duration] => Array ( )
[field_learning_path_enable_forum] => Array ( [0] => Array ( [value] => 0 ) )
[field_learning_path_folder] => Array ( )
[field_learning_path_forum] => Array ( )
[field_learning_path_media_image] => Array ( )
[field_learning_path_published] => Array ( [0] => Array ( [value] => 0 ) )
[field_learning_path_visibility] => Array ( [0] => Array ( [value] => semiprivate ) )
[field_lesson_instructors] => Array ( )
[field_required_trainings] => Array ( )
[field_requires_validation] => Array ( [0] => Array ( [value] => 0 ) )
[field_workspace] => Array ( ) )

  }
    // Wizard step 2: Group membership form.
    else {
      // Create an empty group membership that does not yet have a group set.
      $values = [
        'type' => $group_type->getContentPlugin('group_membership')->getContentTypeConfigId(),
        'entity_id' => $this->currentUser()->id(),
      ];
      $entity = $this->entityTypeManager()->getStorage('group_content')->create($values);
      
      // Log the group membership entity details.
      \Drupal::logger('addForm4')->notice('Group membership entity created: @entity', ['@entity' => print_r($entity->toArray(), TRUE)]);
    }

    // Return the entity form with the configuration gathered above.
    return $this->entityFormBuilder()->getForm($entity, 'add', $extra);
  }

I checked the database schema for groups_field_data:

CREATE TABLE `groups_field_data` (
  `id` int(10) unsigned NOT NULL,
  `revision_id` int(10) unsigned NOT NULL,
  `type` varchar(32) CHARACTER SET ascii NOT NULL COMMENT 'The ID of the target entity.',
  `langcode` varchar(12) CHARACTER SET ascii NOT NULL,
  `uid` int(10) unsigned NOT NULL COMMENT 'The ID of the target entity.',
  `label` varchar(255) DEFAULT NULL,
  `created` int(11) DEFAULT NULL,
  `changed` int(11) DEFAULT NULL,
  `default_langcode` tinyint(4) NOT NULL,
  `status` tinyint(4) NOT NULL,
  `revision_translation_affected` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`,`langcode`),
  KEY `group__id__default_langcode__langcode` (`id`,`default_langcode`,`langcode`),
  KEY `group__revision_id` (`revision_id`),
  KEY `group_field__type__target_id` (`type`),
  KEY `group_field__uid__target_id` (`uid`),
  KEY `group__status_type` (`status`,`type`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The data table for group entities.';

This clearly shows that revision_id is set to NOT NULL without a default value. This means that any INSERT operation into this table must include a value for the revision_id field.

Steps to reproduce

composer file has the following relevant packages:

        "opigno/opigno_lms": "3.1.0", 
        "drupal/group": "1.2.0",
        "drupal/core-recommended": "^9.4",
        "drupal/core": "9.5.10"

Drupal Version
9.5.10
Web Server
nginx/1.21.6
PHP Version
8.1.14
Memory limit
512M
Database Version
10.4.25-MariaDB-log

Proposed resolution

Find out where "revision_id" should exist, and patch it in to where it should exist.

Remaining tasks

n/a

User interface changes

n/a

API changes

n/a

Data model changes

n/a

🐛 Bug report
Status

Active

Version

3.1

Component

Code

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @mtaggart@s-5.com
  • I spun up an older version of Opigno, and Drupal:
    DRUPAL VERSION
    9.4.15
    MEMORY LIMIT
    256M
    DATABASE VERSION
    10.4.25-MariaDB-log
    PHP
    7.4.30
    Opigno LMS VERSION
    3.0.9

    database schema difference

    CREATE TABLE `groups_field_data` (
      `id` int(10) unsigned NOT NULL,
      `type` varchar(32) CHARACTER SET ascii NOT NULL COMMENT 'The ID of the target entity.',
      `langcode` varchar(12) CHARACTER SET ascii NOT NULL,
      `uid` int(10) unsigned NOT NULL COMMENT 'The ID of the target entity.',
      `label` varchar(255) DEFAULT NULL,
      `created` int(11) DEFAULT NULL,
      `changed` int(11) DEFAULT NULL,
      `default_langcode` tinyint(4) NOT NULL,
      PRIMARY KEY (`id`,`langcode`),
      KEY `group__id__default_langcode__langcode` (`id`,`default_langcode`,`langcode`),
      KEY `group_field__type__target_id` (`type`),
      KEY `group_field__uid__target_id` (`uid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The data table for group entities.';

    It seems to me that the following columns were added:

    1. revision_id: An integer column that might be used to store information related to revisions of the entity.
    2. status: A tiny integer column that might be used to store the status of the entity.
    3. revision_translation_affected: A tiny integer column that might be used to indicate whether a revision has affected translations.
  • As a temporary fix to this issue I altered the database with the following query:

    ALTER TABLE `groups_field_data`
      MODIFY `revision_id` int(10) unsigned NOT NULL DEFAULT 0,
      MODIFY `status` tinyint(4) NOT NULL DEFAULT 0,
      MODIFY `revision_translation_affected` tinyint(4) DEFAULT 0;
    

    This allows me to create the training, and does not seem to have negative side effects at this time.

Production build 0.71.5 2024